index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. <template>
  2. <div class="bg" style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; overflow: hidden">
  3. <a-spin :spinning="loading" />
  4. <div id="window3D" style="width: 100%; height: 100%; position: absolute; overflow: hidden"> </div>
  5. <!-- <div id="damper3DCSS" v-show="!loading" style="width: 100%; height: 100%; top:0; left: 0; position: absolute; overflow: hidden;">
  6. <div>
  7. <div ref="elementContent" class="elementContent">
  8. <p><span class="data-title">压力(Pa):</span>{{selectData.frontRearDP}}</p>
  9. <p><span class="data-title">动力源压力(MPa):</span>{{selectData.sourcePressure}}</p>
  10. <p><span class="data-title">故障诊断:</span>
  11. <i
  12. :class="{'state-icon': true, 'open': selectData.messageBoxStatus, 'close': !selectData.messageBoxStatus}"
  13. ></i>{{selectData.fault}}</p>
  14. </div>
  15. </div>
  16. </div> -->
  17. </div>
  18. <div class="scene-box">
  19. <div class="top-box">
  20. <div class="top-center row">
  21. <!-- <div class="button-box" @click="start(0)">复位</div> -->
  22. <!-- <div class="button-box" @click="testPlay()">自测动画</div> -->
  23. <div class="button-box" @click="testPlay('up')">上</div>
  24. <div class="button-box" @click="testPlay('center')">中</div>
  25. <div class="button-box" @click="testPlay('down')">下</div>
  26. <div class="button-box" @click="testPlay('reset')">复位</div>
  27. </div>
  28. <div class="top-right row">
  29. <div class="control-type row">
  30. <div class="control-title">控制模式:</div>
  31. <a-radio-group v-model:value="controlType">
  32. <a-radio :value="1">就地</a-radio>
  33. <a-radio :value="2">远程</a-radio>
  34. </a-radio-group>
  35. </div>
  36. <div class="run-type row">
  37. <div class="control-title">运行状态:</div>
  38. <a-radio-group v-model:value="controlType">
  39. <a-radio :value="1">检修</a-radio>
  40. </a-radio-group>
  41. </div>
  42. <div class="run-state row">
  43. <div class="control-title">网络状态:</div>
  44. <a-radio-group v-model:value="controlType">
  45. <a-radio :value="1">运行</a-radio>
  46. </a-radio-group>
  47. </div>
  48. </div>
  49. </div>
  50. <div class="title-text">
  51. {{ selectData.strname }}
  52. </div>
  53. <div class="bottom-tabs-box">
  54. <div class="tabs-button-group">
  55. <a-button class="tabs-button" type="primary" @click="openModel">一键测风</a-button>
  56. <!-- <a-button class="tabs-button" type="primary" @click="exportExcel()">导出报表</a-button> -->
  57. </div>
  58. <a-tabs class="tabs-box" v-model:activeKey="activeKey" @change="tabChange">
  59. <a-tab-pane key="1" tab="实时监测">
  60. <MonitorTable
  61. columnsType="windrect_monitor"
  62. :dataSource="dataSource"
  63. design-scope="windrect-monitor"
  64. @selectRow="getSelectRow"
  65. title="测风装置监测"
  66. >
  67. <template #filterCell="{ column, record }">
  68. <a-tag v-if="column.dataIndex === 'sign'" :color="record.sign == 0 ? '#95CF65' : record.sign == 1 ? '#4590EA' : '#9876AA'">{{
  69. record.sign == 0 ? '高位' : record.sign == 1 ? '中位' : '低位'
  70. }}</a-tag>
  71. <template v-if="record && column && column.dataIndex === 'isRun' && record.isRun">
  72. <a-tag v-if="record.isRun == -2 || record.isRun == -1" :color="record.isRun == -2 ? '#95CF65' : '#ED5700'">{{
  73. record.isRun == -2 ? '空闲' : '等待'
  74. }}</a-tag>
  75. <a-tag v-else-if="record.isRun == 100" color="#4693FF">完成</a-tag>
  76. <Progress v-else :percent="Number(record.isRun)" size="small" status="active" />
  77. </template>
  78. <!-- <a-tag v-if="column.dataIndex === 'sign'" :color="record.sign == 0 ? '#95CF65' : record.sign == 1 ? '#4590EA' : '#9876AA'">{{
  79. record.sign == 0 ? '高位' : record.sign == 1 ? '中位' : '低位'
  80. }}</a-tag>
  81. <template v-if="record && column && column.dataIndex === 'isRun' && record.isRun">
  82. <a-tag v-if="record.isRun == -2 || record.isRun == -1" :color="record.isRun == -2 ? '#95CF65' : '#ED5700'">{{
  83. record.isRun === -2 ? '空闲' : '等待'
  84. }}</a-tag>
  85. <Progress v-else :percent="Number(record.isRun)" size="small" status="active" />
  86. </template> -->
  87. </template>
  88. </MonitorTable>
  89. </a-tab-pane>
  90. <a-tab-pane key="2" tab="实时曲线图" force-render>
  91. <div class="tab-item" v-if="activeKey === '2'">
  92. <DeviceEcharts
  93. chartsColumnsType="windrect_chart"
  94. xAxisPropType="strname"
  95. :dataSource="dataSource"
  96. height="100%"
  97. :chartsColumns="chartsColumns"
  98. :device-list-api="baseList"
  99. device-type="windrect"
  100. />
  101. </div>
  102. </a-tab-pane>
  103. <a-tab-pane key="3" tab="历史数据">
  104. <div class="tab-item">
  105. <HistoryTable columns-type="windrect_history" device-type="windrect" :device-list-api="baseList" designScope="windrect-history" />
  106. </div>
  107. </a-tab-pane>
  108. <a-tab-pane key="4" tab="报警历史">
  109. <div class="tab-item">
  110. <AlarmHistoryTable columns-type="alarm_history" device-type="windrect" :device-list-api="baseList" designScope="alarm-history" />
  111. </div>
  112. </a-tab-pane>
  113. <a-tab-pane key="5" tab="操作历史">
  114. <div class="tab-item">
  115. <HandlerHistoryTable columns-type="alarm_history" device-type="fanlocal" :device-list-api="baseList" designScope="alarm-history" />
  116. </div>
  117. </a-tab-pane>
  118. <a-tab-pane key="6" tab="测风结果">
  119. <ResultTable v-if="activeKey == 6" deviceType="windrect_list" />
  120. </a-tab-pane>
  121. </a-tabs>
  122. </div>
  123. </div>
  124. <div style="z-index: -1; position: absolute; top: 50px; right: 10px; width: 300px; height: 280px; margin: auto" class="palyer">
  125. <LivePlayer id="cf-player1" ref="player1" :videoUrl="flvURL1()" muted live loading controls />
  126. <LivePlayer id="cf-player2" ref="player2" :videoUrl="flvURL1()" muted live loading controls style="margin-top: 10px" />
  127. </div>
  128. <BasicModal v-bind="$attrs" @register="registerModal" title="一键测风" width="900px" @ok="handleOk" @cancel="handleCancel">
  129. <div class="head-line">
  130. <div class="vent-flex-row">
  131. <span>同时运行数量:</span>
  132. <a-input-number v-model:value="runNum" :min="1" :max="10" />
  133. </div>
  134. <div class="vent-flex-row">
  135. <div v-for="(criticalPath, index) in criticalPathList" :key="index" class="button-box" @click="selectCriticalPath(criticalPath.id)">{{
  136. criticalPath.systemname
  137. }}</div>
  138. </div>
  139. </div>
  140. <div>
  141. <ModalTable ref="modalTable" deviceType="windrect_list" />
  142. </div>
  143. </BasicModal>
  144. </template>
  145. <script setup lang="ts">
  146. import DeviceEcharts from '../comment/DeviceEcharts.vue';
  147. import { onBeforeMount, computed, ComputedRef, ref, onMounted, onUnmounted, reactive, toRaw, toRef, toRefs, Ref } from 'vue';
  148. import { BasicModal, useModalInner } from '/@/components/Modal';
  149. import MonitorTable from '../comment/MonitorTable.vue';
  150. import ModalTable from './components/modalTable.vue';
  151. import ResultTable from './components/resultTable.vue';
  152. import HistoryTable from '../comment/HistoryTable.vue';
  153. import AlarmHistoryTable from '../comment/AlarmHistoryTable.vue';
  154. import HandlerHistoryTable from '../comment/HandlerHistoryTable.vue';
  155. import { initWebSocket, getRecordList } from '/@/hooks/web/useVentWebSocket';
  156. import { deviceControlApi } from '/@/api/vent/index';
  157. import { mountedThree, destroy, addFmText, play, setModelType } from './windrect.threejs';
  158. import LivePlayer from '@liveqing/liveplayer-v3';
  159. import { list, pathList, deviceList, testWind } from './windrect.api';
  160. import { list as baseList } from '../../deviceManager/windfindingTabel/windfinding.api';
  161. import { message, Progress } from 'ant-design-vue';
  162. import { chartsColumns } from './windrect.data';
  163. const modalTable = ref();
  164. const runNum = ref(5); //设备运行数量
  165. const criticalPathList = ref([]);
  166. const player1 = ref<HTMLDivElement | null>(null);
  167. const player2 = ref<HTMLDivElement | null>(null);
  168. const activeKey = ref('1');
  169. const loading = ref(false);
  170. // 默认初始是第一行
  171. const selectRowIndex = ref(0);
  172. // 监测数据
  173. const selectData = reactive({
  174. deviceID: '',
  175. deviceType: '',
  176. strname: '',
  177. dataDh: '-', //压差
  178. dataDtestq: '-', //测试风量
  179. sourcePressure: '-', //气源压力
  180. dataDequivalarea: '-',
  181. netStatus: '0', //通信状态
  182. fault: '气源压力超限',
  183. sign: -1
  184. });
  185. const flvURL1 = () => {
  186. return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`;
  187. };
  188. const flvURL2 = () => {
  189. return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`;
  190. };
  191. // const dataSource = computed(() => {
  192. // const data = [...getRecordList()] || [];
  193. // Object.assign(selectData, toRaw(data[selectRowIndex.value]));
  194. // addFmText(selectData);
  195. // return data;
  196. // });
  197. const dataSource = ref([]);
  198. const tabChange = (activeKeyVal) => {
  199. activeKey.value = activeKeyVal;
  200. };
  201. // 设备数据
  202. const controlType = ref(1);
  203. // https获取监测数据
  204. let timer: null | NodeJS.Timeout = null;
  205. const getMonitor = () => {
  206. if (Object.prototype.toString.call(timer) === '[object Null]') {
  207. timer = setTimeout(() => {
  208. list({ devicetype: 'windrect', pagetype: 'normal' }).then((res) => {
  209. dataSource.value = res.msgTxt[0].datalist || [];
  210. dataSource.value.forEach((data: any) => {
  211. const readData = data.readData;
  212. data = Object.assign(data, readData);
  213. });
  214. const data: any = toRaw(dataSource.value[selectRowIndex.value]); //maxarea
  215. Object.assign(selectData, data);
  216. addFmText(selectData);
  217. // 根据3个点位分别执行动画
  218. if(data.deviceType == "windrect_fold"){
  219. if (selectData['apparatusRun'] == 1) {
  220. const flag = selectData.sign == 0 ? 'up' : selectData.sign == 1 ? 'center' : selectData.sign == 2 ? 'down' : null
  221. if (flag) play(flag);
  222. } else {
  223. const flag = selectData.sign == 1 ? 'center' : selectData.sign == 2 ? 'down' : null
  224. if (flag) play(flag, true);
  225. }
  226. }
  227. if(data.deviceType == "windrect_rect"){
  228. if (selectData['apparatusRun'] == 1) {
  229. const flag = selectData.sign == 0 ? 'center' : selectData.sign == 1 ? 'down' : selectData.sign == 2 ? 'up' : null
  230. if (flag) play(flag);
  231. } else {
  232. const flag = selectData.sign == 1 ? 'center' : selectData.sign == 2 ? 'down' : selectData.sign == 0 ? 'up': null
  233. if (flag) play(flag, true);
  234. }
  235. }
  236. if (data.deviceType == "windrect_ds") {
  237. if (selectData['apparatusRun'] == 1) {
  238. play('start')
  239. }else{
  240. play('start', true)
  241. }
  242. }
  243. if (timer) {
  244. timer = null;
  245. }
  246. getMonitor();
  247. });
  248. }, 1000);
  249. }
  250. };
  251. // 自测动画方法
  252. const testPlay = (flag) => {
  253. play(flag);
  254. // play('start')
  255. // setTimeout(() => {
  256. // play('up')
  257. // }, 0)
  258. // setTimeout(() => {
  259. // play('center')
  260. // }, 10000)
  261. // setTimeout(() => {
  262. // play('down')
  263. // }, 40000)
  264. // setTimeout(() => {
  265. // play('up')
  266. // }, 60000)
  267. };
  268. // 切换检测数据
  269. const getSelectRow = async (selectRow, index) => {
  270. loading.value = true;
  271. selectRowIndex.value = index;
  272. // Object.assign(selectData, selectRow);
  273. const type = selectRow.deviceType == 'windrect_rect' ? 'lmWindRect' : selectRow.deviceType == 'windrect_fold' ? 'zdWindRect' : 'dsWindRect';
  274. // const type = selectRowIndex.value >= 1 ? 'lmWindRect' : selectRowIndex.value <= 3 ? 'zdWindRect' : 'dsWindRect';
  275. await setModelType(type);
  276. loading.value = false;
  277. };
  278. const start = (flag) => {
  279. const data = {
  280. deviceid: selectData.deviceID,
  281. devicetype: selectData.deviceType,
  282. paramcode: flag == 1 ? 'testStart' : '',
  283. };
  284. deviceControlApi(data).then((res) => {
  285. if (res.success) {
  286. //
  287. }
  288. });
  289. };
  290. const addPlayVideo = (event) => {
  291. event.preventDefault();
  292. if (player1.value.play && player2.value.play) {
  293. player1.value.play();
  294. player2.value.play();
  295. document.body.removeEventListener('mousedown', addPlayVideo);
  296. }
  297. };
  298. //表单赋值
  299. const [registerModal, { setModalProps, closeModal }] = useModalInner();
  300. /* 一键测风 */
  301. const handleOk = () => {
  302. const ids = toRaw(modalTable.value.selectedRowKeys);
  303. testWind({ ids: ids, maxnum: runNum.value }).then((res) => {
  304. message.success(res);
  305. setModalProps({ visible: false });
  306. modalTable.value.clearSelectedRowKeys();
  307. });
  308. };
  309. /* 打开一键测风弹窗 */
  310. const openModel = () => {
  311. setModalProps({ visible: true });
  312. };
  313. const exportExcel = (id) => {
  314. exportXls({ testid: id })
  315. }
  316. /* 关闭一键测风弹窗 */
  317. const handleCancel = () => {
  318. setModalProps({ visible: false });
  319. modalTable.value.clearSelectedRowKeys();
  320. };
  321. const getPathList = async () => {
  322. const pathArr = await pathList({});
  323. criticalPathList.value = pathArr.records.filter((item) => {
  324. return item.strsystype == 3;
  325. });
  326. };
  327. /* 根据路线选择测风装置 */
  328. const selectCriticalPath = (pathId) => {
  329. deviceList({ deviceType: 'wind', sysId: pathId }).then((res) => {
  330. const ids: string[] = [];
  331. res.records.forEach((item) => {
  332. ids.push(item.id);
  333. });
  334. if (modalTable.value) modalTable.value.setSelectedRowKeys(ids);
  335. });
  336. };
  337. onBeforeMount(() => {
  338. // const sendVal = JSON.stringify({ pagetype: 'normal', devicetype: 'windrect', orgcode: '', ids: '', systemID: '' });
  339. // initWebSocket(sendVal);
  340. getPathList();
  341. });
  342. onMounted(() => {
  343. loading.value = true;
  344. mountedThree(player1.value, player2.value).then(async () => {
  345. getMonitor();
  346. // loading.value = false;
  347. });
  348. document.body.addEventListener('mousedown', addPlayVideo, false);
  349. });
  350. onUnmounted(() => {
  351. destroy();
  352. if (timer) {
  353. clearTimeout(timer);
  354. timer = undefined;
  355. }
  356. });
  357. </script>
  358. <style scoped lang="less">
  359. @import '/@/design/vent/modal.less';
  360. :deep(.ant-tabs-tabpane-active) {
  361. overflow: auto;
  362. }
  363. .head-line {
  364. display: flex;
  365. flex-direction: row;
  366. justify-content: space-between;
  367. .button-box {
  368. position: relative;
  369. padding: 5px;
  370. border: 1px transparent solid;
  371. border-radius: 5px;
  372. margin-left: 8px;
  373. margin-right: 8px;
  374. width: auto;
  375. height: 34px;
  376. border: 1px solid #65dbea;
  377. display: flex;
  378. align-items: center;
  379. justify-content: center;
  380. color: #fff;
  381. padding: 0 15px;
  382. cursor: pointer;
  383. pointer-events: auto;
  384. &:hover {
  385. background: linear-gradient(#3eb2ff55, #00c3ff55);
  386. }
  387. &::before {
  388. width: calc(100% - 6px);
  389. height: 26px;
  390. content: '';
  391. position: absolute;
  392. top: 3px;
  393. right: 0;
  394. left: 3px;
  395. bottom: 0;
  396. z-index: -1;
  397. border-radius: inherit; /*important*/
  398. background: linear-gradient(#014978, #3bf3fc);
  399. }
  400. }
  401. }
  402. .ant-tabs-tabpane {
  403. overflow-y: auto !important;
  404. }
  405. </style>