european-gourd.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. <template>
  2. <div class="g-p20 g-mb-40 content">
  3. <base-head :titles="titles.arr" @onTabChange="change" bottom="20"></base-head>
  4. <div class="g-card g-p20 g-fx main" v-loading="dataSource.loading">
  5. <div class="left">
  6. <div class="left__head">
  7. <div class="info">
  8. <div class="info1">基本信息</div>
  9. <div class="info2">{{ equipmentInfo.name }}</div>
  10. <div class="info3">
  11. <span>设备序列号 </span> <span class="g-ml-20">{{ equipmentInfo.code }}</span>
  12. </div>
  13. <div class="info3">
  14. <span>监控编号</span>
  15. <span class="g-ml-20">
  16. {{ equipmentInfo.productNo }}
  17. </span>
  18. </div>
  19. <div class="info3">
  20. <span>设备型号</span> <span class="g-ml-20">{{ equipmentInfo.model }}</span>
  21. </div>
  22. </div>
  23. <div class="info__img" :class="{ isOnLine: !equipmentInfo?.isOnline }"></div>
  24. <div class="head__second">
  25. <div class="second-info">
  26. <div class="second-name">
  27. {{ '报警 ' + alarmList.array.length }}
  28. </div>
  29. <div class="second-alarm"><live-message :list="alarmList.array"></live-message></div>
  30. </div>
  31. <div class="second-info">
  32. <div class="second-name bg2">
  33. {{ '预警 ' + warningList.array.length }}
  34. </div>
  35. <div class="second-warning"><live-message :list="warningList.array"></live-message></div>
  36. </div>
  37. </div>
  38. </div>
  39. <div class="left__footer g-p20">
  40. <div class="footer__info g-fx">
  41. <div class="item" v-if="currentData?.stroke">
  42. <div>
  43. <div class="text1">
  44. {{ formatValue(currentData?.stroke.value, currentData?.stroke.translate) }}
  45. <span style="font-weight: 400; font-size: 16px">{{ currentData?.stroke.unit }}</span>
  46. </div>
  47. <div class="text2">
  48. {{ currentData?.isHoist ? '高度' : '行程' }}
  49. </div>
  50. </div>
  51. </div>
  52. <div class="item" v-if="currentData?.gear">
  53. <div>
  54. <div class="text1">
  55. {{ formatGears(currentData?.gear) }}
  56. <span style="font-weight: 400; font-size: 16px">{{ currentData?.gear.unit }}</span>
  57. </div>
  58. <div class="text2">
  59. {{ '档位' }}
  60. </div>
  61. </div>
  62. </div>
  63. <div class="item" v-if="currentData?.weight">
  64. <div>
  65. <div class="text1">
  66. {{ formatValue(currentData?.weight.value, currentData?.weight.translate) }}
  67. <span style="font-weight: 400; font-size: 16px">{{ currentData?.weight.unit }}</span>
  68. </div>
  69. <div class="text2">
  70. {{ '载荷' }}
  71. <div class="weight-color" :style="{ backgroundColor: formatWeightLight(currentData?.weight.light) }">
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. </div>
  77. <!-- 中间六个指示灯 -->
  78. <div class="g-fx footer__info2">
  79. <div class="item2" v-for="el in currentData?.middleLightList" :key="el.variableId">
  80. <div class="text1" :style="{ backgroundColor: getBgColor(el) }"></div>
  81. <div class="text2">{{ el.variableName }}</div>
  82. </div>
  83. </div>
  84. </div>
  85. </div>
  86. <div class="right">
  87. <div class="g-fx-j right__item" v-for="(el, i) in currentData?.switchVariables" :key="i">
  88. <div class="text">{{ el.variableName }}</div>
  89. <div class="text2">
  90. <div :style="{ backgroundColor: getBgColor(el) }"></div>
  91. </div>
  92. </div>
  93. <div class="g-fx-j right__item" v-for="(el, i) in currentData?.matchVariables" :key="i">
  94. <div class="text">{{ el.name }}</div>
  95. <div class="text2">
  96. {{ formatValue(el.vValue, el.translate) + el.unitName }}
  97. <div :style="{ backgroundColor: getMatchBgColor(el) }"></div>
  98. </div>
  99. </div>
  100. <div class="g-fx-j right__item" v-for="(el, i) in currentData?.valueVariables" :key="i">
  101. <div class="text">{{ el.variableName }}</div>
  102. <div class="text2">
  103. {{ formatValue(el.value, el.translate) + el.unitName }}
  104. </div>
  105. </div>
  106. </div>
  107. </div>
  108. </div>
  109. </template>
  110. <script setup lang="ts">
  111. import { getGroupAndVariables } from '@/api'
  112. import { fetchDeviceInfoAll } from '@/api/deviceAuthorize'
  113. import { useSidebarStore } from '@/store/sidebar'
  114. import { formatGears, formatValue, formatWeightLight } from '@/utils/tools'
  115. import LiveMessage from './live-message.vue'
  116. /** 警告数组 */
  117. const warningList: any = reactive({ array: [] })
  118. /** 报警数组 */
  119. const alarmList: any = reactive({ array: [] })
  120. let titles = reactive({
  121. arr: []
  122. })
  123. const sidebar = useSidebarStore()
  124. let equipmentInfo: any = ref({})
  125. let deviceId: any = ref('')
  126. let currentData: any = ref({})
  127. let dataSource: any = reactive({
  128. data: []
  129. })
  130. onUnmounted(() => {
  131. onUnsubscribe(`Byte/${equipmentInfo.value.code}`)
  132. })
  133. onMounted(async () => {
  134. const eId = useQuery('id')
  135. sidebar.setEquipmentId(eId)
  136. deviceId.value = useQuery('id') as string
  137. getDeviceInfo()
  138. getDataSource()
  139. })
  140. const getDeviceInfo = async () => {
  141. const data = await fetchDeviceInfoAll(deviceId.value)
  142. equipmentInfo.value = data
  143. onSubscribe([`Byte/${equipmentInfo.value.code}`],(topic: any, payload: any) => {
  144. parseData(payload)
  145. })
  146. }
  147. const getDataSource = async () => {
  148. const data = await getGroupAndVariables({ deviceId: deviceId.value })
  149. dataSource.data = data
  150. titles.arr = dataSource.data.map((item: { groupName: any }) => item.groupName)
  151. dataSource.data.forEach((group: any) => {
  152. group.middleLightList = group.switchVariables.slice(0, 6)
  153. group.switchVariables = group.switchVariables.slice(6)
  154. var light = group.weight?.light
  155. if (light != null) {
  156. updateAlarm('red', group.groupName + '超载', light.value)
  157. }
  158. group.middleLightList.forEach((g: any) => {
  159. updateAlarm(g.trueLight, group.groupName + g.variableName, g.value)
  160. })
  161. group.switchVariables.forEach((g: any) => {
  162. updateAlarm(g.trueLight, group.groupName + g.variableName, g.value)
  163. })
  164. })
  165. currentData.value = dataSource.data[0]
  166. }
  167. const change = (v: any, position: any) => {
  168. currentData.value = dataSource.data[position]
  169. }
  170. const parseData = (payload: any) => {
  171. const obj = JSON.parse(payload)
  172. //设备号
  173. const station = obj.Station
  174. //数据
  175. const byteArray = base64ToArray(obj.Data);
  176. for (var index = 0; index < dataSource.data.length; index++) {
  177. const item = dataSource.data[index]
  178. if (station != item.station) {
  179. continue
  180. }
  181. //载荷
  182. var weight = item.weight
  183. if (weight != null) {
  184. weight.value = toData(byteArray, weight.dataType, weight.address)
  185. var light = weight.light
  186. if (light != null) {
  187. light.value = light.isReverse != toData(byteArray, 1, light.address)
  188. updateAlarm('red', item.groupName + '超载', light.value)
  189. }
  190. }
  191. //高度或者 行程
  192. var stroke = item.stroke
  193. if (stroke != null) {
  194. var value = toData(byteArray, stroke.dataType, stroke.address)
  195. stroke.value = value < 0 ? '0' : value
  196. }
  197. //档位
  198. var gear = item.gear
  199. if (gear != null) {
  200. var array: any = []
  201. gear.options.forEach((option: any) => {
  202. var gearName = option.displayName
  203. var address = option.address
  204. var b = toData(byteArray, option.dataType, address)
  205. if (b) {
  206. array.push(gearName)
  207. }
  208. })
  209. gear.gears = array
  210. }
  211. item.middleLightList.forEach((m: any) => {
  212. m.value = m.isReverse != toData(byteArray, 1, m.address)
  213. updateAlarm(m.trueLight, item.groupName + m.variableName, m.value)
  214. })
  215. item.switchVariables.forEach((s: any) => {
  216. s.value = s.isReverse != toData(byteArray, 1, s.address)
  217. updateAlarm(s.trueLight, item.groupName + s.variableName, s.value)
  218. })
  219. item.valueVariables.forEach((e: any) => {
  220. const value = toData(byteArray, e.dataType, e.address)
  221. e.value = evaluateFormula(formatValue(value, e.translate), e.formula)
  222. })
  223. item.matchVariables.forEach((m: any) => {
  224. m.vValue = toData(byteArray, m.dataType, m.vAddress)
  225. var sAddress = m.sAddress
  226. if (sAddress.indexOf(',') > 0) {
  227. var addressArray = sAddress.split(',')
  228. for (var index = 0; index < addressArray.length; index++) {
  229. const addr = addressArray[index]
  230. var b = m.isReverse != toData(byteArray, 1, addr)
  231. if (b) {
  232. m.sValue = true
  233. updateAlarm(m.trueLight, item.groupName + m.sName, true)
  234. break
  235. }
  236. }
  237. m.sValue = false
  238. } else {
  239. m.sValue = m.isReverse != toData(byteArray, 1, sAddress)
  240. updateAlarm(m.trueLight, item.groupName + m.sName, m.sValue)
  241. }
  242. })
  243. }
  244. }
  245. const evaluateFormula = (value: any, formula: any) => {
  246. if (formula == null || formula == undefined || formula === '') {
  247. return value
  248. }
  249. //函数模板
  250. const array = formula.split(",")
  251. //字符串模本
  252. var templete = array[0]
  253. const formulaArray = array.slice(1)
  254. formulaArray.forEach((item: any, index: number) => {
  255. const func = item.replace("{Value}", value)
  256. const result = eval(func)
  257. templete = templete.replace(`{${index}}`, Math.floor(parseFloat(result)))
  258. })
  259. return templete
  260. }
  261. const base64ToArray = (str: string) => {
  262. const base64Str = window.atob(str);
  263. const len = base64Str.length;
  264. const bytes = new Uint8Array(len)
  265. for (var i = 0; i < len; i++) {
  266. bytes[i] = base64Str.charCodeAt(i)
  267. }
  268. return bytes;
  269. }
  270. const toData = (bytes: any, dataType: number, address: string) => {
  271. switch (dataType) {
  272. case 1: //布尔值
  273. var positionArray = address.split('.')
  274. if (positionArray.length > 0) {
  275. //字节位置
  276. var groupPosition = parseInt(positionArray[0])
  277. //值
  278. var childPosition = parseInt(positionArray[1])
  279. if (groupPosition <= bytes.length - 1) {
  280. return byte2Boolen(bytes[groupPosition])[childPosition]
  281. }
  282. }
  283. return ''
  284. case 2: //16-bit Unsigned
  285. var position = parseInt(address)
  286. return toUInt16(bytes, position)
  287. case 3: //16-bit Signed
  288. var position = parseInt(address)
  289. return toInt16(bytes, position)
  290. case 4: //32-bit Unsigned
  291. case 5:
  292. var position = parseInt(address)
  293. return toUInt32(bytes, position)
  294. case 6: //32-bit Float
  295. var position = parseInt(address)
  296. return toUInt32(bytes, position) << 0
  297. default:
  298. return ''
  299. }
  300. }
  301. const byte2Boolen = (byte: any) => {
  302. var bin: any = []
  303. byte
  304. .toString(2)
  305. .padStart(8, '0')
  306. .split('')
  307. .reverse()
  308. .forEach((c: any) => {
  309. bin.push(c == '1' ? true : false)
  310. })
  311. return bin
  312. }
  313. const toUInt16 = (bytes: any, offset: number) => {
  314. if (bytes.length < 2) return 0
  315. return parseInt(bytes[offset + 1].toString(16) + bytes[offset].toString(16), 16)
  316. }
  317. const toInt16 = (bytes: any, offset: number) => {
  318. var num = toUInt16(bytes, offset)
  319. return num > 32767 ? num - 65536 : num
  320. }
  321. const toUInt32 = (bytes: any, offset: number) => {
  322. if (bytes.length < 4) return 0
  323. var num = parseInt(
  324. bytes[offset + 1].toString(16) +
  325. bytes[offset + 0].toString(16) +
  326. bytes[offset + 3].toString(16) +
  327. bytes[offset + 2].toString(16),
  328. 16
  329. )
  330. return num
  331. }
  332. const updateAlarm = (trueLight: string, name: string, value: any) => {
  333. if (value == true || value == 'true') {
  334. if (trueLight == 'red') {
  335. var alarm = alarmList.array.find((f: any) => f == name)
  336. if (alarm == null) {
  337. alarmList.array.push(name)
  338. }
  339. }
  340. if (trueLight == 'yellow') {
  341. var warn = warningList.array.find((f: any) => f == name)
  342. if (warn == null) {
  343. warningList.array.push(name)
  344. }
  345. }
  346. } else {
  347. alarmList.array = alarmList.array.filter((f: any) => f != name)
  348. warningList.array = warningList.array.filter((f: any) => f != name)
  349. }
  350. }
  351. /**
  352. *
  353. * @param el 传入的元素
  354. * @param isFirst 判断是不是 单独的三个
  355. */
  356. const getBgColor = (el: any) => {
  357. const obj: any = {
  358. green: '#6AD28C',
  359. red: '#FE5656',
  360. yellow: '#F49F51',
  361. gray: '#999999'
  362. }
  363. if (el.value || el.value === 'true') {
  364. return obj[el.trueLight]
  365. } else {
  366. return obj[el.falseLight]
  367. }
  368. }
  369. /**
  370. *
  371. * @param el 传入的元素
  372. * @param isFirst 判断是不是 单独的三个
  373. */
  374. const getMatchBgColor = (el: any) => {
  375. const obj: any = {
  376. green: '#6AD28C',
  377. red: '#FE5656',
  378. yellow: '#F49F51',
  379. gray: '#999999'
  380. }
  381. // 是否翻转 true 翻转, false 不翻转
  382. if (el.sValue || el.sValue === 'true') {
  383. return obj[el.trueLight]
  384. } else {
  385. return obj[el.falseLight]
  386. }
  387. }
  388. </script>
  389. <style lang="less" scoped>
  390. .content {
  391. background-color: #c8cfdf;
  392. height: 100%;
  393. }
  394. .left {
  395. width: 100%;
  396. border-right: 1px solid #e5e7ee;
  397. display: flex;
  398. flex-direction: column;
  399. &__head {
  400. width: calc(100% - 20px);
  401. display: flex;
  402. flex-direction: row;
  403. height: 260px;
  404. padding: 0px 30px;
  405. align-items: center;
  406. background: linear-gradient(36deg, #86b0e9, #5f7cd6);
  407. border-radius: 15px;
  408. justify-content: space-between;
  409. .info {
  410. color: #fff;
  411. }
  412. .info__img {
  413. background-image: url('../../../assets/img/equipment2.png');
  414. background-size: 100% 100%;
  415. width: 300px;
  416. height: 300px;
  417. }
  418. .isOnLine {
  419. filter: grayscale(100%);
  420. }
  421. .info1 {
  422. background-color: rgba(42, 55, 110, 0.4);
  423. font-weight: bold;
  424. font-size: 16px;
  425. width: 87px;
  426. padding: 5px;
  427. text-align: center;
  428. border-radius: 5px;
  429. }
  430. .info2 {
  431. margin: 15px 0;
  432. font-weight: bold;
  433. font-size: 24px;
  434. }
  435. .info3 {
  436. margin: 5px 0;
  437. font-size: 12px;
  438. span:first-child {
  439. display: inline-block;
  440. width: 60px;
  441. }
  442. }
  443. }
  444. .head__second {
  445. width: 260px;
  446. min-width: 260px;
  447. margin-left: 20px;
  448. border-radius: 10px;
  449. .second-info {
  450. height: 75px;
  451. display: flex;
  452. border-radius: 15px;
  453. background: RGBA(229, 193, 164, 0.5);
  454. border: 1px solid #c49159;
  455. align-items: center;
  456. padding-left: 20px;
  457. }
  458. ::v-deep(.second-info) {
  459. .el-carousel__indicator {
  460. display: none;
  461. }
  462. }
  463. .second-info:first-child {
  464. margin-bottom: 20px;
  465. background: RGBA(229, 163, 169, 0.5);
  466. border: 1px solid #cd524a;
  467. }
  468. .second-name {
  469. width: 70px;
  470. height: 45px;
  471. background: #cd524a;
  472. border-radius: 10px;
  473. color: #fff;
  474. text-align: center;
  475. line-height: 45px;
  476. font-size: 12px;
  477. margin-right: 10px;
  478. }
  479. .second-alarm {
  480. color: #c44b44;
  481. font-size: 12px;
  482. width: 120px;
  483. }
  484. .second-warning {
  485. color: #c57d31;
  486. font-size: 12px;
  487. width: 120px;
  488. }
  489. .bg2 {
  490. background: #d79146;
  491. }
  492. .second-time {
  493. font-size: 12px;
  494. margin: 3px;
  495. text-align: center;
  496. }
  497. .color2 {
  498. color: #c57d31;
  499. }
  500. .second-text {
  501. align-items: center;
  502. justify-content: center;
  503. margin: 3px;
  504. }
  505. .icon-right {
  506. display: flex;
  507. align-items: center;
  508. .el-icon {
  509. font-size: 12px;
  510. transform: scale(0.9);
  511. }
  512. }
  513. .second-text {
  514. font-size: 12px;
  515. }
  516. }
  517. &__footer {
  518. display: flex;
  519. flex-direction: column;
  520. margin-right: 20px;
  521. border: 3px solid #e5e7ee;
  522. border-radius: 15px;
  523. margin-top: 20px;
  524. margin-bottom: 20px;
  525. .footer__info {
  526. min-height: 170px;
  527. align-items: center;
  528. }
  529. .item {
  530. flex: 1;
  531. height: 100%;
  532. text-align: center;
  533. display: flex;
  534. flex-direction: column;
  535. justify-content: center;
  536. border-right: 2px solid #e5e7ee;
  537. &:last-child {
  538. border: none;
  539. }
  540. .text1 {
  541. font-size: 36px;
  542. font-weight: bold;
  543. }
  544. .weight-color {
  545. position: absolute;
  546. width: 20px;
  547. height: 4px;
  548. border-radius: 2px;
  549. position: absolute;
  550. left: calc(50% + 20px);
  551. top: 50%;
  552. transform: translateY(-50%);
  553. }
  554. .text2 {
  555. color: #4a5373;
  556. margin-top: 8px;
  557. position: relative;
  558. }
  559. }
  560. .footer__info2 {
  561. margin-top: 60px;
  562. margin-bottom: 30px;
  563. }
  564. .item2 {
  565. flex: 1;
  566. text-align: center;
  567. .text1 {
  568. width: 80px;
  569. height: 7px;
  570. background: #6ad28c;
  571. border-radius: 3px;
  572. margin: 0 auto;
  573. }
  574. .text2 {
  575. color: #4a5373;
  576. font-size: 14px;
  577. margin-top: 10px;
  578. }
  579. }
  580. }
  581. }
  582. .right {
  583. width: 490px;
  584. height: 100%;
  585. padding-left: 16px;
  586. &__item {
  587. height: 40px;
  588. margin: 0 30px;
  589. line-height: 40px;
  590. border-bottom: 1px solid #dcddde;
  591. margin-bottom: 5px;
  592. .text {
  593. color: #4a5373;
  594. font-size: 14px;
  595. }
  596. .text2 {
  597. display: flex;
  598. align-items: center;
  599. justify-content: center;
  600. div {
  601. width: 30px;
  602. height: 5px;
  603. background: #6ad28c;
  604. border-radius: 3px;
  605. margin-left: 10px;
  606. }
  607. }
  608. }
  609. }
  610. </style>