fireTS.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. <!-- eslint-disable vue/multi-word-component-names -->
  2. <template>
  3. <div class="company-home">
  4. <!-- 顶部标题样式块 -->
  5. <div class="top-bg">
  6. <div class="main-title">{{ mainTitle }}</div>
  7. </div>
  8. <!-- 中间样式块 -->
  9. <div class="center-info-bar">
  10. <div class="left-info-content">
  11. <div class="left-block block1">
  12. <div class="info-value">{{ fireSgWarnInfo.sgCoTbAlarmAddress }}</div>
  13. <div class="info-label">突变预警</div>
  14. </div>
  15. <div class="left-block block2">
  16. <div class="info-value">{{ fireSgWarnInfo.nyAlarmLevelStr }}</div>
  17. <div class="info-label">煤自燃氧化阶段</div>
  18. </div>
  19. <div class="left-block block3">
  20. <div class="info-value">{{ fireSgWarnInfo.sgMaxTemp }}</div>
  21. <div class="info-label">最高温度</div>
  22. </div>
  23. </div>
  24. <div class="center-info-content">
  25. <div class="info-value">{{ fireSgWarnInfo.aqfxLevelStr }}</div>
  26. <div class="info-label">火灾安全等级</div>
  27. </div>
  28. <div class="right-info-content">
  29. <div class="right-block block1">
  30. <div class="info-value">{{ fireSgWarnInfo.sgZbAlarmNum }}</div>
  31. <div class="info-label">指标预警</div>
  32. </div>
  33. <div class="right-block block2">
  34. <div class="info-value">{{ fireSgWarnInfo.yjjbLevelStr }}</div>
  35. <div class="info-label">预警等级</div>
  36. </div>
  37. <div class="right-block block3">
  38. <div class="info-value">{{ fireSgWarnInfo.sgSwAlarmAddress }}</div>
  39. <div class="info-label">升温预警</div>
  40. </div>
  41. </div>
  42. </div>
  43. <!-- 渲染所有模块 -->
  44. <ModuleCommon
  45. v-for="cfg in cfgs"
  46. :key="cfg.deviceType + cfg.moduleName"
  47. :show-style="cfg.showStyle"
  48. :module-data="cfg.moduleData"
  49. :module-name="cfg.moduleName"
  50. :device-type="cfg.deviceType"
  51. :data="data"
  52. :visible="true"
  53. />
  54. <ModuleCommonDual
  55. v-if="cfgA && cfgB"
  56. :show-style="cfgA.showStyle"
  57. :module-data-a="cfgA.moduleData"
  58. :module-name-a="cfgA.moduleName"
  59. :device-type-a="cfgA.deviceType"
  60. :module-data-b="cfgB.moduleData"
  61. :module-name-b="cfgB.moduleName"
  62. :device-type-b="cfgB.deviceType"
  63. :module-data-c="cfgC.moduleData"
  64. :module-name-c="cfgC.moduleName"
  65. :device-type-c="cfgC.deviceType"
  66. :data="data"
  67. :visible="true"
  68. :common-title="commonTitle"
  69. />
  70. <!-- 运维反应首页卡顿先行注释掉,后续再调整 -->
  71. <!-- <Three3D :modal-name="modalName" /> -->
  72. </div>
  73. </template>
  74. <script lang="ts" setup>
  75. import { computed, onMounted, onUnmounted, ref } from 'vue';
  76. import { useInitConfigs, useInitPage } from './hooks/useInit';
  77. import { getAlarmRecord, getDeviceSys } from './configurable.api';
  78. import { testConfigTSFire } from './configurable.data.tashan';
  79. import ModuleCommon from './components/ModuleCommon.vue';
  80. import ModuleCommonDual from './components/ModuleCommonDual.vue';
  81. // import Three3D from './components/three3D.vue';
  82. // const modalName = ref('workFace11');
  83. const cfgs = computed(() => configs.value.filter((_, index) => index !== 6 && index !== 7 && index !== 8));
  84. const cfgA = computed<any>(() => configs.value[6]);
  85. const cfgB = computed<any>(() => configs.value[7]);
  86. const cfgC = computed<any>(() => configs.value[8]);
  87. const { configs, fetchConfigs } = useInitConfigs();
  88. const { mainTitle, data, updateData } = useInitPage('回采工作面智能管控');
  89. let interval: ReturnType<typeof setInterval> | undefined;
  90. const commonTitle = '实时监测与预警';
  91. const fireSgWarnInfo = ref({
  92. aqfxLevelStr: '-',
  93. nyAlarmLevel: 0,
  94. nyAlarmLevelStr: '-',
  95. sgCoTbAlarmAddress: '-',
  96. sgMaxTemp: '-',
  97. sgSwAlarmAddress: '-',
  98. sgZbAlarmNum: 0,
  99. yjjbLevelStr: '-',
  100. });
  101. onMounted(() => {
  102. let alarmLogData = [];
  103. fetchConfigs('ts_fire').then(() => {
  104. configs.value = testConfigTSFire;
  105. getDeviceSys({
  106. devicetype: 'sys',
  107. type: 'all',
  108. }).then((res) => {
  109. const processedRes = handleData(res);
  110. getAlarmRecord({
  111. sysId: '1955807282207465474',
  112. devicekind: 'bundletube',
  113. pageNo: '1',
  114. pageSize: '10',
  115. }).then((res) => {
  116. alarmLogData = res.records || [];
  117. processedRes.alarmLog = alarmLogData;
  118. updateData(processedRes);
  119. });
  120. });
  121. });
  122. interval = setInterval(() => {
  123. getDeviceSys({
  124. devicetype: 'sys',
  125. type: 'all',
  126. }).then((res) => {
  127. const processedRes = handleData(res);
  128. processedRes.alarmLog = alarmLogData;
  129. updateData(processedRes);
  130. });
  131. }, 2000);
  132. });
  133. const handleData = (res: any) => {
  134. const processedData = { ...res };
  135. fireSgWarnInfo.value = res?.fireSgWarnInfo || {};
  136. // 处理束管数据,分类进风、回风
  137. if (processedData.deviceInfo?.bundletube) {
  138. const { datalist = [] } = processedData.deviceInfo.bundletube;
  139. processedData.deviceInfo.bundletube = {
  140. ...processedData.deviceInfo.bundletube,
  141. enterWind: datalist.filter((item) => item.strRemark === 'enterWind'),
  142. returnWind: datalist.filter((item) => item.strRemark === 'returnWind'),
  143. };
  144. }
  145. // 处理光纤测温数据
  146. if (processedData.deviceInfo?.fiber?.datalist) {
  147. processedData.deviceInfo.fiber.datalist.forEach((item) => {
  148. const tempData = item.readData.fibreTemperature;
  149. if (typeof tempData === 'string') {
  150. try {
  151. item.readData.fibreTemperature = JSON.parse(tempData || '[]');
  152. } catch (e) {
  153. // 解析失败时的容错处理
  154. console.error('解析JSON失败:', e);
  155. item.readData.fibreTemperature = []; // 给个默认值
  156. }
  157. }
  158. });
  159. }
  160. // 处理束管数据
  161. if (processedData.deviceInfo?.bundletube?.datalist) {
  162. // 每次处理前先清空chartData,避免数据累积
  163. chartData.value = [];
  164. const bundletubeDataList = processedData.deviceInfo.bundletube.datalist;
  165. // 处理selectorConfig1:遍历datalist,提取deviceID和strinstallpos
  166. selectorConfig1 = {
  167. options: bundletubeDataList.map((device) => ({
  168. value: device.deviceID, // 选项值:设备唯一ID
  169. label: device.strinstallpos, // 选项标签:设备
  170. })),
  171. };
  172. const targetIndicators: Array<keyof Pick<BundletubeHistoryItem, 'ch2val' | 'chval' | 'co2val' | 'coval' | 'gasval' | 'o2val'>> = [
  173. 'ch2val',
  174. 'chval',
  175. 'co2val',
  176. 'coval',
  177. 'gasval',
  178. 'o2val',
  179. ];
  180. // 遍历datalist(每个item对应一个设备安装位置)
  181. bundletubeDataList.forEach((deviceItem) => {
  182. const deviceID = deviceItem.deviceID; // 设备ID
  183. const deviceHistory = deviceItem.history; // 当前设备的history数组
  184. // 跳过history为空的情况
  185. if (!deviceHistory || !Array.isArray(deviceHistory)) return;
  186. // 遍历每个指标,生成对应的chartData项
  187. targetIndicators.forEach((indicator) => {
  188. // 收集当前指标的seriesData(时间+数值)
  189. const seriesData: SeriesDataItem[] = deviceHistory
  190. .map((historyItem: BundletubeHistoryItem) => ({
  191. time: historyItem.time,
  192. value: historyItem[indicator], // 匹配当前指标的数值
  193. }))
  194. .filter((item) => item.time); // 过滤掉无时间的无效数据
  195. // 推送到最终chartData
  196. chartData.value.push({
  197. deviceID,
  198. sensorId: indicator,
  199. seriesData,
  200. });
  201. });
  202. });
  203. processedData.deviceInfo.bundletube.chartConfig = {
  204. selectorConfig1: selectorConfig1,
  205. selectorConfig2: selectorConfig2,
  206. chartData: chartData,
  207. };
  208. }
  209. return processedData;
  210. };
  211. interface BundletubeHistoryItem {
  212. time: string; // 时间戳(如"2025-08-23 07:56:35")
  213. ch2val: string | number; // 甲烷2值
  214. chval: string | number; // 甲烷值
  215. co2val: string | number; // 二氧化碳值
  216. coval: string | number; // 一氧化碳值
  217. gasval: string | number; // 瓦斯值
  218. o2val: string | number; // 氧气值
  219. }
  220. // 定义chartData中seriesData的单个元素类型
  221. interface SeriesDataItem {
  222. time: string;
  223. value: string | number; // 对应指标的数值
  224. }
  225. interface ChartDataItem {
  226. deviceID: string; // 设备id
  227. sensorId: 'ch2val' | 'chval' | 'co2val' | 'coval' | 'gasval' | 'o2val'; // 指标ID(固定6个)
  228. seriesData: SeriesDataItem[]; // 时间+数值数组
  229. }
  230. // 处理selectorConfig1:遍历datalist,提取deviceID和strinstallpos
  231. let selectorConfig1 = {
  232. options: [
  233. {
  234. value: '', // 选项值:设备唯一Id
  235. label: '', // 设备名
  236. },
  237. ],
  238. };
  239. // 处理selectorConfig2:固定6个指标ID及对应中文标签
  240. const selectorConfig2 = {
  241. options: [
  242. { value: 'o2val', label: '氧气' },
  243. { value: 'coval', label: '一氧化碳' },
  244. { value: 'co2val', label: '二氧化碳' },
  245. { value: 'chval', label: '甲烷' },
  246. { value: 'ch2val', label: '乙烷' },
  247. { value: 'gasval', label: '瓦斯' },
  248. ],
  249. };
  250. // 最终生成的图表数据
  251. const chartData = ref<ChartDataItem[]>([]);
  252. // 数据处理函数
  253. onUnmounted(() => {
  254. clearInterval(interval);
  255. });
  256. </script>
  257. <style lang="less" scoped>
  258. @import '/@/design/theme.less';
  259. @font-face {
  260. font-family: 'douyuFont';
  261. src: url('../../../../assets/font/douyuFont.otf');
  262. }
  263. .company-home {
  264. --image-fire-title: url(/@/assets/images/vent/vent-header1.png);
  265. --image-common-border1: url('/@/assets/images/home-container/configurable/minehome/common-border1.png');
  266. --image-common-border3: url('/@/assets/images/home-container/configurable/minehome/common-border3.png');
  267. width: 100%;
  268. height: 100%;
  269. color: @white;
  270. position: relative;
  271. background: #09172c;
  272. .top-bg {
  273. width: 100%;
  274. height: 73px;
  275. background: var(--image-fire-title) no-repeat top;
  276. position: absolute;
  277. z-index: 1;
  278. .main-title {
  279. height: 80px;
  280. font-family: 'douyuFont';
  281. font-size: 26px;
  282. letter-spacing: 2px;
  283. display: flex;
  284. justify-content: center;
  285. align-items: center;
  286. padding: 0 0 10px 0;
  287. }
  288. }
  289. // 顶部中间样式块
  290. .center-info-bar {
  291. position: relative;
  292. top: 75px;
  293. left: 50%;
  294. transform: translateX(-50%);
  295. width: 900px;
  296. height: 160px;
  297. display: flex;
  298. align-items: center;
  299. justify-content: center;
  300. background: url('@/assets/images/home-container/configurable/tashanhome/center-bar-bg.png') no-repeat center;
  301. z-index: 1;
  302. .center-info-content {
  303. position: relative;
  304. top: 15px;
  305. background: url('@/assets/images/home-container/configurable/tashanhome/center-bar-circle.png') no-repeat center;
  306. width: 160px;
  307. height: 160px;
  308. text-align: center;
  309. .info-value {
  310. position: absolute;
  311. top: 34%;
  312. left: 50%;
  313. transform: translateX(-50%);
  314. font-family: 'douyuFont';
  315. font-size: 25px;
  316. color: #2cffdd;
  317. }
  318. .info-label {
  319. width: 100%;
  320. position: absolute;
  321. bottom: 25px;
  322. font-size: 17px;
  323. left: 50%;
  324. transform: translateX(-50%);
  325. }
  326. }
  327. .left-info-content {
  328. position: relative;
  329. text-align: right;
  330. .left-block {
  331. position: absolute;
  332. width: 190px;
  333. height: 55px;
  334. background: url('@/assets/images/home-container/configurable/tashanhome/leftbar-bg1.png') no-repeat right;
  335. padding-right: 52px;
  336. .info-value {
  337. height: 28px;
  338. line-height: 28px;
  339. font-family: 'douyuFont';
  340. font-size: 12px;
  341. color: var(--vent-gas-primary-text);
  342. }
  343. .info-label {
  344. height: 28px;
  345. line-height: 28px;
  346. }
  347. }
  348. .block1 {
  349. top: -70px;
  350. right: -5px;
  351. }
  352. .block2 {
  353. top: -15px;
  354. left: -280px;
  355. background: url('@/assets/images/home-container/configurable/tashanhome/leftbar-bg2.png') no-repeat right;
  356. .info-value {
  357. color: #2cffdd;
  358. }
  359. }
  360. .block3 {
  361. top: 40px;
  362. right: -5px;
  363. }
  364. }
  365. .right-info-content {
  366. position: relative;
  367. .right-block {
  368. position: absolute;
  369. width: 190px;
  370. height: 55px;
  371. background: url('@/assets/images/home-container/configurable/tashanhome/rightbar-bg1.png') no-repeat left;
  372. padding-left: 52px;
  373. .info-value {
  374. height: 28px;
  375. line-height: 28px;
  376. font-family: 'douyuFont';
  377. font-size: 12px;
  378. color: var(--vent-gas-primary-text);
  379. }
  380. .info-label {
  381. height: 28px;
  382. line-height: 28px;
  383. }
  384. }
  385. .block1 {
  386. top: -70px;
  387. left: -5px;
  388. }
  389. .block2 {
  390. top: -15px;
  391. right: -280px;
  392. background: url('@/assets/images/home-container/configurable/tashanhome/rightbar-bg2.png') no-repeat left;
  393. .info-value {
  394. color: #2cffdd;
  395. }
  396. }
  397. .block3 {
  398. top: 40px;
  399. left: -5px;
  400. }
  401. }
  402. }
  403. ::v-deep .dane-bd {
  404. background-repeat: no-repeat;
  405. background-position: center;
  406. background-size: 100% 100%;
  407. &.dane-w {
  408. background-image: var(--image-common-border3);
  409. }
  410. .dane-title {
  411. justify-content: space-around;
  412. padding: 0 50px 0 0;
  413. .common-navL {
  414. font-size: 14px;
  415. font-weight: bold;
  416. font-family: 'douyuFont';
  417. }
  418. }
  419. .dane-content {
  420. border: none;
  421. background: none;
  422. padding: 10px 35px;
  423. }
  424. }
  425. ::v-deep .table__content .table__content_list {
  426. width: 95%;
  427. }
  428. ::v-deep .table__content .table__content_label {
  429. width: 95%;
  430. }
  431. }
  432. </style>