shuifeizs.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. <template>
  2. <view class="facilitystate-page">
  3. <custom-card>
  4. <block slot="backText">
  5. <view class="facilitystate-top__title">
  6. <text :class="devStatus == '1' ? 'online' : 'offline'">{{
  7. devStatus == '1' ? '在线' : '离线'
  8. }}</text>
  9. {{ devName }}
  10. </view>
  11. </block>
  12. </custom-card>
  13. <view class="facilitystate-top">
  14. <view
  15. class="facilitystate-top__item"
  16. v-for="(item, index) in deviceList"
  17. :key="index"
  18. @click="nativeTo(item)"
  19. >
  20. <view class="item-icon-container" :class="item.className">
  21. <image class="item-icon" :src="item.icon" />
  22. </view>
  23. <view class="facilitystate-top__item-text">{{ item.title }}</view>
  24. </view>
  25. </view>
  26. <view class="irrMode">
  27. <view class="irrMode__header">
  28. <view @click="changeMode('1')" :class="workStatus == '1' ? 'active' : ''">施肥模式</view>
  29. <view @click="changeMode('0')" :class="workStatus == '0' ? 'active' : ''">灌溉模式</view>
  30. </view>
  31. <view class="irrMode__content">
  32. <view class="irrMode__content-header">
  33. <view class="irrMode__content-header-list">
  34. <view class="irrMode__content-header-title">
  35. <view class="irrMode__content-header-container">
  36. <image class="header-item-icon" :src="rain" />
  37. <text style="color:#333;font-size: 24rpx;margin-left: 10rpx">当前灌溉区域:</text>{{ runTitle }}
  38. </view>
  39. <view class="timer-text">{{ timer }}分</view>
  40. </view>
  41. </view>
  42. <view class="irrMode__content-item">
  43. <view class="irrMode__content-item-raduis" :class="runStatus == '1' ? 'active-radius' : ''" @click="changeActive('1')">
  44. <image class="item-icon" :src="runStatus=='1'?mediaPlayFill:mediaPlay" />
  45. <view class="item-text">启动</view>
  46. </view>
  47. <view class="irrMode__content-item-raduis" :class="runStatus == '2' ? 'active-radius' : ''" @click="changeActive('2')">
  48. <image class="item-icon" :src="runStatus=='2'?mediaPauseFill:mediaPause" />
  49. <view class="item-text">暂停</view>
  50. </view>
  51. <view class="irrMode__content-item-raduis" :class="runStatus == '0' ? 'active-radius' : ''" @click="changeActive('0')" style="margin-right:0">
  52. <image class="item-icon" :src="runStatus=='0'?stopFill:stop" />
  53. <view class="item-text">停止</view>
  54. </view>
  55. </view>
  56. <view class="irr-run-list">
  57. <text v-for="(item, index) in selectAreaList" :key="index">
  58. {{ item.group_name }}
  59. <text v-if="index != selectAreaList.length - 1">、</text>
  60. </text>
  61. </view>
  62. </view>
  63. </view>
  64. <Base :dataList="dataList" />
  65. <facilitystate
  66. :devBid="devBid"
  67. :ggbCurrent="ggbCurrent"
  68. :sfbCurrent="sfbCurrent"
  69. :irrigatedAreaList="irrigatedAreaList"
  70. :webSockedData="webSockedData"
  71. :alreadyfertilizerBucketList="alreadyfertilizerBucketList"
  72. />
  73. </view>
  74. </view>
  75. </template>
  76. <script>
  77. import manualControl from './assets/manualControl.png';
  78. import mediaPause from './assets/media-pause.png';
  79. import mediaPauseFill from './assets/media-pause-fill.png';
  80. import mediaPlay from './assets/media-play.png';
  81. import mediaPlayFill from './assets/media-play-fill.png';
  82. import rain from './assets/rain.png';
  83. import stop from './assets/stop.png';
  84. import stopFill from './assets/stop-fill.png';
  85. import wheelIrrigation from './assets/wheelIrrigation.png';
  86. import time from './assets/timing.png';
  87. import formulaSetting from './assets/formulaSetting.png';
  88. import operatingRecord from './assets/operatingRecord.png';
  89. import Base from './components/base.vue';
  90. import facilitystate from './components/facilitystate.vue';
  91. export default {
  92. data() {
  93. return {
  94. ws: null,
  95. currentMode: '1',
  96. title: '水肥一体机',
  97. manualControl,
  98. devBid: '',
  99. devName: '',
  100. devStatus: '1',
  101. dataList: [],
  102. irrigatedAreaList: [],
  103. alreadyfertilizerBucketList: [],
  104. mediaPause,
  105. mediaPauseFill,
  106. mediaPlay,
  107. mediaPlayFill,
  108. rain,
  109. stop,
  110. stopFill,
  111. deviceList: [
  112. {
  113. icon: manualControl,
  114. title: '手动控制',
  115. className: 'manualControl',
  116. url: '/pages/cb/shuifeizsFirst/control',
  117. },
  118. {
  119. icon: wheelIrrigation,
  120. title: '自动控制',
  121. className: 'wheelIrrigation',
  122. url: '/pages/cb/shuifeizsFirst/autoSetting?devBid=' + this.devBid,
  123. }, {
  124. icon: time,
  125. title: '定时设置',
  126. className: 'timedSetting',
  127. url: '/pages/cb/shuifeizsFirst/timingSetting?devBid=' + this.devBid,
  128. },{
  129. icon: formulaSetting,
  130. title: '配方设置',
  131. className: 'formulaSetting',
  132. url: '/pages/cb/shuifeizsFirst/formulaSetting?devBid=' + this.devBid,
  133. }, {
  134. icon: operatingRecord,
  135. title: '操作记录',
  136. className: 'operatingRecord',
  137. url: '/pages/cb/shuifeizsFirst/history?devBid=' + this.devBid,
  138. },
  139. ],
  140. ggbCurrent: {},
  141. sfbCurrent: {},
  142. sfToken: '',
  143. entityId: '',
  144. heartbeatTimer: null,
  145. info: {},
  146. dataArray:[],
  147. webSockedData: {},
  148. reconnectCount: 0,
  149. isActive: '0',
  150. runStatus: -1,
  151. workStatus: -1,
  152. clearIrr: {},
  153. selectAreaList: [],
  154. runTitle: '--',
  155. timer:'--'
  156. };
  157. },
  158. components: {
  159. Base,
  160. facilitystate,
  161. },
  162. onLoad(options) {
  163. /*
  164. 0 水源 泵类 负责水源进入管道的总泵或者阀
  165. 1 肥料 泵类 负责肥料进入管道的总阀或者泵
  166. 2 吸肥 泵类 控制肥料桶出肥的阀或者泵,每个肥料桶一个
  167. 3 搅拌 泵类 肥料桶的搅拌电机或者泵 每个肥料桶一个
  168. 4 肥料桶 泵类 肥料桶,每个施肥机有多个或者没有,不一定真实存在,只是逻辑上的概念
  169. 5 电磁阀 无 管道最末端每个田地里控制出水的阀门
  170. 6 传感器 无 水肥机上的 温度,压力,流速,PH EC等监测类要素
  171. 7 灌区 无 逻辑区域,电磁阀的分组
  172. */
  173. const { devBid, devName, devStatus } = options;
  174. this.devBid = devBid;
  175. this.devName = devName;
  176. this.devStatus = devStatus;
  177. this.deviceList[0].url = `/pages/cb/shuifeizsFirst/control?devBid=${options.devBid}`;
  178. this.deviceList[1].url = `/pages/cb/shuifeizsFirst/autoSetting?devBid=${options.devBid}`;
  179. this.deviceList[2].url = `/pages/cb/shuifeizsFirst/timingSetting?devBid=${options.devBid}`;
  180. this.deviceList[3].url = `/pages/cb/shuifeizsFirst/formulaSetting?devBid=${options.devBid}`;
  181. this.deviceList[
  182. this.deviceList.length - 1
  183. ].url = `/pages/cb/shuifeizsFirst/history?devBid=${options.devBid}`;
  184. if (devBid) {
  185. this.init();
  186. }
  187. },
  188. onUnload() {
  189. this.ws.close();
  190. },
  191. onShow(){
  192. if (this.devBid) {
  193. this.init();
  194. }
  195. },
  196. methods: {
  197. isHaveTime(childrenList) {
  198. let time = 0;
  199. let currentKey = '';
  200. let currentItem = {};
  201. for (let i = 0; i < this.selectAreaList.length; i++) {
  202. for (let key in this.selectAreaList[i]) {
  203. if (key.includes(':Status')) {
  204. currentKey = key;
  205. currentItem = this.selectAreaList[i];
  206. break;
  207. }
  208. }
  209. for (let i = 0; i < childrenList.length; i++) {
  210. const item = childrenList[i];
  211. if (item.sfCode === currentKey) {
  212. for (let key in currentItem) {
  213. if (key.includes(':PartTim')) {
  214. time = currentItem[key];
  215. }
  216. }
  217. break;
  218. }
  219. }
  220. }
  221. return time;
  222. },
  223. isAutoRun(childrenList) {
  224. for (let i = 0; i < childrenList.length; i++) {
  225. const item = childrenList[i];
  226. if (item.value == 1) {
  227. return true
  228. }
  229. }
  230. return false
  231. },
  232. async initsfyunshangAutoConfigInfo() {
  233. const selectAreaList = [];
  234. const res = await this.$myRequest({
  235. url:'/api/v2/iot/device/sf/yunshang/auto/config/info/',
  236. method:'post',
  237. data: {
  238. devBid: String(this.devBid),
  239. },
  240. })
  241. const list = res;
  242. const group_list = list.group_list || [];
  243. group_list.forEach((item) => {
  244. if (item.group_value == 1) {
  245. selectAreaList.push(item);
  246. }
  247. });
  248. this.selectAreaList = selectAreaList;
  249. },
  250. async devctlContorl(data){
  251. const params = {
  252. devBid: Number(this.devBid),
  253. data,
  254. };
  255. const res = await this.$myRequest({
  256. url: '/api/v2/iot/mobile/device/sf/devctl/',
  257. method: 'post',
  258. data:params,
  259. header: {
  260. 'Content-Type': 'application/json',
  261. },
  262. });
  263. uni.showToast({
  264. title: res.msg,
  265. icon: 'none',
  266. });
  267. },
  268. changeActive(active) {
  269. this.runStatus = active;
  270. const params = {
  271. IrrStatus:active
  272. }
  273. this.devctlContorl(params);
  274. },
  275. changeMode(mode) {
  276. if(this.runStatus == '1' || this.runStatus == '2'){
  277. uni.showToast({
  278. title: '请先停止运行',
  279. icon: 'none',
  280. });
  281. return
  282. }
  283. this.currentMode = mode;
  284. const params = {
  285. IrrMode:mode
  286. }
  287. this.devctlContorl(params);
  288. },
  289. initWebSocket() {
  290. const url = 'wss://things.ysiot.net:18080/api/ws';
  291. this.ws = uni.connectSocket({
  292. url: url,
  293. success: () => {
  294. console.log('WebSocket 连接初始化成功');
  295. }
  296. });
  297. uni.onSocketOpen(() => {
  298. console.log('WebSocket 已连接');
  299. // 发送测试参数
  300. const testParams = {
  301. cmds: [
  302. {
  303. type: 'TIMESERIES',
  304. entityType: 'DEVICE',
  305. entityId: this.entityId,
  306. scope: 'LATEST_TELEMETRY',
  307. cmdId: 1
  308. }
  309. ],
  310. authCmd: {
  311. cmdId: 0,
  312. token: this.sfToken,
  313. }
  314. };
  315. console.log('发送测试参数:', testParams);
  316. uni.sendSocketMessage({
  317. data: JSON.stringify(testParams)
  318. });
  319. // 心跳:每 30 秒 ping 一次
  320. this.heartbeatTimer = setInterval(() => {
  321. uni.sendSocketMessage({
  322. data: JSON.stringify({ type: 'ping' })
  323. });
  324. }, 30 * 1000);
  325. });
  326. uni.onSocketMessage((evt) => {
  327. try {
  328. const data = JSON.parse(evt.data);
  329. this.webSockedData = data;
  330. // 收到实时数据后刷新右侧组件
  331. if(this.dataArray.length){
  332. this.mergeTwoObject(this.dataArray,this.webSockedData?.data);
  333. this.initData(this.dataArray);
  334. }
  335. } catch (e) {
  336. console.error('WebSocket 消息解析失败', e);
  337. }
  338. });
  339. uni.onSocketError((e) => {
  340. console.error('WebSocket 错误', e);
  341. });
  342. uni.onSocketClose(() => {
  343. console.warn('WebSocket 已断开,3 秒后尝试重连');
  344. clearInterval(this.heartbeatTimer);
  345. this.reconnectCount = (this.reconnectCount || 0) + 1;
  346. if (this.reconnectCount <= 10) {
  347. setTimeout(() => this.initWebSocket(), 3000);
  348. } else {
  349. console.warn('WebSocket 重连次数已达上限,停止重连');
  350. }
  351. });
  352. },
  353. mergeTwoObject(firstObject, secondObject) {
  354. // 构建 sfCode 到对象的映射,实现 O(1) 查找
  355. const sfCodeMap = new Map();
  356. // 递归构建映射
  357. const buildMap = (items) => {
  358. for (const item of items) {
  359. if (item.sfCode) {
  360. sfCodeMap.set(item.sfCode, item);
  361. }
  362. if (item.childrenList && item.childrenList.length) {
  363. buildMap(item.childrenList);
  364. }
  365. }
  366. };
  367. buildMap(firstObject);
  368. // 遍历 secondObject 并更新值
  369. for (const key in secondObject) {
  370. let newKey = '';
  371. if(key.includes('GHChannelDev:ChannelParamSet')){
  372. newKey = key + ':Status';
  373. const item = sfCodeMap.get(newKey);
  374. if (item) {
  375. const params = JSON.parse(secondObject[key][0][1]);
  376. this.$set(item,'value',params.Status)
  377. }
  378. }else{
  379. const item = sfCodeMap.get(key);
  380. if (item) {
  381. this.$set(item,'value',secondObject[key][0][1])
  382. }
  383. }
  384. }
  385. },
  386. // 关闭 WebSocket
  387. closeWebSocket() {
  388. clearInterval(this.heartbeatTimer);
  389. if (this.ws) {
  390. uni.closeSocket();
  391. this.ws = null;
  392. }
  393. },
  394. async getsfrhinfo() {
  395. const res = await this.$myRequest({
  396. url: '/api/v2/iot/device/sf/info/',
  397. method: 'post',
  398. data: {
  399. devBid: String(this.devBid),
  400. },
  401. });
  402. this.info = res;
  403. this.sfToken = this.info?.sfToken;
  404. this.entityId = this.info?.sfUuid;
  405. this.closeWebSocket();
  406. this.initWebSocket();
  407. },
  408. async init() {
  409. this.getdeviceSfStatus();
  410. await this.getpeifangRefresh();
  411. this.getsfrhinfo();
  412. this.initsfyunshangAutoConfigInfo();
  413. await this.getRunStatus();
  414. },
  415. async getpeifangRefresh() {
  416. await this.$myRequest({
  417. url: '/api/v2/iot/mobile/device/sf/peifang/refresh/',
  418. method: 'post',
  419. data: {
  420. devBid: this.devBid,
  421. },
  422. });
  423. },
  424. async getRunStatus() {
  425. const res = await this.$myRequest({
  426. url: '/api/v2/iot/mobile/device/sf/zsrf/task/run/status/',
  427. method: 'post',
  428. data: {
  429. devBid: this.devBid,
  430. },
  431. });
  432. const data = JSON.stringify(res || {});
  433. if (data == '{}') {
  434. this.isRun = true;
  435. } else {
  436. this.isRun = false;
  437. }
  438. },
  439. initData(res){
  440. this.dataList = [];
  441. this.irrigatedAreaList = [];
  442. this.alreadyfertilizerBucketList = [];
  443. res?.forEach((item) => {
  444. if (item.sfType === '0') {
  445. this.ggbCurrent = item;
  446. } else if (item.sfType === '1') {
  447. this.sfbCurrent = item;
  448. } else if (item.sfType === '4') {
  449. this.alreadyfertilizerBucketList.push(item);
  450. } else if (item.sfType === '6') {
  451. this.dataList.push(item);
  452. } else if (item.sfType === '7') {
  453. this.irrigatedAreaList.push(item);
  454. } else if (item.sfType === '9') {
  455. if (item.sfCode === 'IrrStatus') {
  456. this.runStatus = item.value;
  457. } else if (item.sfCode === 'IrrMode') {
  458. this.workStatus = item.value;
  459. } else if (item.sfCode === 'WaterFlowSum:Value') {
  460. this.clearIrr = item;
  461. }
  462. }
  463. });
  464. this.runTitle = '--';
  465. this.timer = '--';
  466. if(this.runStatus == '1'){
  467. this.irrigatedAreaList.forEach((item) => {
  468. if (this.isAutoRun(item.childrenList)) {
  469. this.runTitle = item.sfDisplayname || item.sfName;
  470. this.timer = this.isHaveTime(item.childrenList);
  471. }
  472. });
  473. }else{
  474. this.runTitle = '--';
  475. this.timer = '--';
  476. }
  477. },
  478. async getdeviceSfStatus() {
  479. const res = await this.$myRequest({
  480. url: '/api/v2/iot/mobile/device/sf/status/',
  481. method: 'post',
  482. data: {
  483. devBid: this.devBid,
  484. },
  485. });
  486. this.dataArray = res || [];
  487. this.initData(this.dataArray);
  488. },
  489. nativeTo(item) {
  490. if(item.className == 'manualControl'){
  491. if(this.runStatus == '1' || this.runStatus == '2'){
  492. uni.showToast({
  493. title: '请先停止运行',
  494. icon: 'none',
  495. });
  496. return
  497. }
  498. }
  499. uni.navigateTo({
  500. url: item.url,
  501. });
  502. },
  503. },
  504. };
  505. </script>
  506. <style scoped lang="scss">
  507. uni-page-body {
  508. position: relative;
  509. height: 100%;
  510. }
  511. .irrMode{
  512. margin-top: 30rpx;
  513. border-radius: 32rpx 32rpx 0 0;
  514. margin-bottom: 30rpx;
  515. padding: 32rpx;
  516. border: 3rpx solid #FFF;
  517. background: linear-gradient(180deg, #D3F3F2 0%, #FFF 41.06%);
  518. .irrMode__header{
  519. display: flex;
  520. width:320rpx;
  521. padding:0 10rpx;
  522. align-items: center;
  523. background: #ffffff;
  524. border-radius: 12rpx;
  525. margin-bottom: 24rpx;
  526. font-family: "Source Han Sans CN VF";
  527. font-size: 28rpx;
  528. font-style: normal;
  529. font-weight: 400;
  530. line-height: normal;
  531. color:#999999;
  532. height: 68rpx;
  533. line-height: 68rpx;
  534. view{
  535. width: 160rpx;
  536. text-align: center;
  537. }
  538. }
  539. .active{
  540. display: flex;
  541. align-items: center;
  542. justify-content: center;
  543. border-radius: 8rpx;
  544. color: #ffffff;
  545. background: #0BBC58;
  546. height: 56rpx;
  547. }
  548. .irrMode__content{
  549. margin-top: 24rpx;
  550. border-radius: 16rpx;
  551. padding: 2rpx;
  552. background: linear-gradient(180deg, #FFF 0%, #F5F6FA 100%);
  553. .irrMode__content-header{
  554. width: calc(100% - 32rpx);
  555. padding: 16rpx 16rpx 16rpx 16rpx;
  556. border-radius: 12rpx;
  557. background: linear-gradient(180deg, #BBF1EA 0%, #E6F4F9 100%);
  558. .irrMode__content-header-list{
  559. display:flex;
  560. justify-content: space-between;
  561. align-items: center;
  562. margin-bottom: 12rpx;
  563. }
  564. .irrMode__content-header-title{
  565. color: #020305;
  566. text-align: right;
  567. font-family: "Source Han Sans CN VF";
  568. font-size: 30rpx;
  569. font-weight: 500;
  570. display: flex;
  571. align-items: center;
  572. justify-content: space-between;
  573. width: 100%;
  574. .irrMode__content-header-container{
  575. display: flex;
  576. align-items: center;
  577. }
  578. .header-item-icon{
  579. width: 48rpx;
  580. height: 48rpx;
  581. }
  582. .timer-text{
  583. color: #0bbc58;
  584. text-align: right;
  585. font-family: "Source Han Sans CN VF";
  586. font-size: 28rpx;
  587. font-weight: 400;
  588. }
  589. }
  590. .irrMode__content-header-desc{
  591. color: #999999;
  592. text-align: right;
  593. font-family: "Source Han Sans CN VF";
  594. font-size: 28rpx;
  595. font-weight: 400;
  596. }
  597. }
  598. .irrMode__content-item{
  599. display:flex;
  600. align-items: center;
  601. justify-content: center;
  602. padding: 16rpx 0;
  603. border-radius: 12rpx;
  604. .irrMode__content-item-raduis{
  605. display: flex;
  606. height: 88rpx;
  607. padding: 0rpx 32rpx;
  608. flex-direction: column;
  609. justify-content: center;
  610. align-items: center;
  611. flex: 1 0 0;
  612. border-radius: 16rpx;
  613. margin-right: 16rpx;
  614. border: 2rpx solid #0BBC58;
  615. color: #0bbc58;
  616. text-align: right;
  617. font-family: "Source Han Sans CN VF";
  618. font-size: 20rpx;
  619. image{
  620. width: 48rpx;
  621. height: 48rpx;
  622. }
  623. }
  624. .active-radius{
  625. background: #0BBC58;
  626. color:#ffffff;
  627. }
  628. }
  629. .irr-run-list{
  630. padding: 16rpx;
  631. font-family: "Source Han Sans CN VF";
  632. color: #999999;
  633. font-family: "Source Han Sans CN VF";
  634. font-size: 24rpx;
  635. font-weight: 400;
  636. }
  637. }
  638. }
  639. .online {
  640. height: 32rpx;
  641. line-height: 32rpx;
  642. padding: 0 8rpx;
  643. border-radius: 4rpx;
  644. background: #14a478;
  645. color: #ffffff;
  646. font-family: 'Source Han Sans CN';
  647. font-size: 20rpx;
  648. font-weight: 500;
  649. margin-right: 8rpx;
  650. top: -4rpx;
  651. display:inline-block;
  652. position: relative;
  653. }
  654. .manualControl{
  655. background: linear-gradient(326deg, #FFAB3D 6.86%, #FED55A 93.52%);
  656. }
  657. .wheelIrrigation{
  658. background: linear-gradient(152deg, #83D2FF 14.82%, #02A2FC 92.92%);
  659. }
  660. .timedSetting{
  661. background: linear-gradient(335deg, #0BBC58 6.91%, #60D799 89.95%);
  662. }
  663. .formulaSetting{
  664. background: linear-gradient(147deg, #FE9797 12.77%, #FF6060 92.63%);
  665. }
  666. .operatingRecord{
  667. background: linear-gradient(152deg, #B9B9F6 11.22%, #8080F8 94.92%);
  668. }
  669. .facilitystate-top__title{
  670. overflow: hidden;
  671. white-space: nowrap;
  672. text-overflow: ellipsis;
  673. }
  674. .offline {
  675. height: 32rpx;
  676. line-height: 32rpx;
  677. padding: 0 8rpx;
  678. border-radius: 4rpx;
  679. background: #ff4d4f;
  680. color: #ffffff;
  681. font-family: 'Source Han Sans CN';
  682. font-size: 20rpx;
  683. font-weight: 500;
  684. margin-right: 8rpx;
  685. top: -4rpx;
  686. display:inline-block;
  687. position: relative;
  688. }
  689. .facilitystate-page {
  690. background: linear-gradient(180deg, #f5f6fa00 0%, #F5F6FA 23.46%, #FFF 25.24%, #FFF 100%), linear-gradient(102deg, #BFEADD 6.77%, #B8F1E7 40.15%, #B9EEF5 84.02%);
  691. height: 100%;
  692. width: 100%;
  693. overflow-x: hidden;
  694. overflow-y: scroll;
  695. .facilitystate-top {
  696. display: grid;
  697. grid-template-columns: repeat(5, 1fr);
  698. text-align: center;
  699. margin-top: 18rpx;
  700. &__item {
  701. margin-bottom: 32rpx;
  702. .item-icon-container{
  703. width: 96rpx;
  704. height: 96rpx;
  705. border-radius: 26rpx;
  706. overflow: hidden;
  707. margin: 0 auto;
  708. display:flex;
  709. align-items: center;
  710. justify-content: center;
  711. }
  712. .item-icon {
  713. width: 64rpx;
  714. height: 64rpx;
  715. }
  716. }
  717. &__item-text {
  718. color: #042118;
  719. font-family: 'Source Han Sans CN VF';
  720. font-size: 28rpx;
  721. font-weight: 400;
  722. margin-top: 16rpx;
  723. }
  724. }
  725. }
  726. </style>