index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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">导出报表</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. <Progress v-else :percent="Number(record.isRun)" size="small" status="active" />
  76. </template>
  77. </template>
  78. </MonitorTable>
  79. </a-tab-pane>
  80. <a-tab-pane key="2" tab="实时曲线图" force-render>
  81. <div class="tab-item" v-if="activeKey === '2'">
  82. <DeviceEcharts
  83. chartsColumnsType="windrect_chart"
  84. xAxisPropType="strname"
  85. :dataSource="dataSource"
  86. height="100%"
  87. :chartsColumns="chartsColumns"
  88. :device-list-api="baseList"
  89. device-type="windrect"
  90. />
  91. </div>
  92. </a-tab-pane>
  93. <a-tab-pane key="3" tab="历史数据">
  94. <div class="tab-item">
  95. <HistoryTable columns-type="windrect_history" device-type="windrect" :device-list-api="baseList" designScope="windrect-history" />
  96. </div>
  97. </a-tab-pane>
  98. <a-tab-pane key="4" tab="报警历史">
  99. <div class="tab-item">
  100. <AlarmHistoryTable columns-type="alarm_history" device-type="windrect" :device-list-api="baseList" designScope="alarm-history" />
  101. </div>
  102. </a-tab-pane>
  103. <a-tab-pane key="5" tab="操作历史">
  104. <div class="tab-item">
  105. <HandlerHistoryTable columns-type="alarm_history" device-type="fanlocal" :device-list-api="baseList" designScope="alarm-history" />
  106. </div>
  107. </a-tab-pane>
  108. <a-tab-pane key="6" tab="测风结果">
  109. <ResultTable v-if="activeKey == 6" deviceType="windrect_list" />
  110. </a-tab-pane>
  111. </a-tabs>
  112. </div>
  113. </div>
  114. <div style="z-index: -1; position: absolute; top: 50px; right: 10px; width: 300px; height: 280px; margin: auto" class="palyer">
  115. <LivePlayer id="cf-player1" ref="player1" :videoUrl="flvURL1()" muted live loading controls />
  116. <LivePlayer id="cf-player2" ref="player2" :videoUrl="flvURL1()" muted live loading controls style="margin-top: 10px" />
  117. </div>
  118. <BasicModal v-bind="$attrs" @register="registerModal" title="一键测风" width="900px" @ok="handleOk" @cancel="handleCancel">
  119. <div class="head-line">
  120. <div class="vent-flex-row">
  121. <span>同时运行数量:</span>
  122. <a-input-number v-model:value="runNum" :min="1" :max="10" />
  123. </div>
  124. <div class="vent-flex-row">
  125. <div v-for="(criticalPath, index) in criticalPathList" :key="index" class="button-box" @click="selectCriticalPath(criticalPath.id)">{{
  126. criticalPath.systemname
  127. }}</div>
  128. </div>
  129. </div>
  130. <div>
  131. <ModalTable ref="modalTable" deviceType="windrect_list" />
  132. </div>
  133. </BasicModal>
  134. </template>
  135. <script setup lang="ts">
  136. import DeviceEcharts from '../comment/DeviceEcharts.vue';
  137. import { onBeforeMount, computed, ComputedRef, ref, onMounted, onUnmounted, reactive, toRaw, toRef, toRefs, Ref } from 'vue';
  138. import { BasicModal, useModalInner } from '/@/components/Modal';
  139. import MonitorTable from '../comment/MonitorTable.vue';
  140. import ModalTable from './components/modalTable.vue';
  141. import ResultTable from './components/resultTable.vue';
  142. import HistoryTable from '../comment/HistoryTable.vue';
  143. import AlarmHistoryTable from '../comment/AlarmHistoryTable.vue';
  144. import HandlerHistoryTable from '../comment/HandlerHistoryTable.vue';
  145. import { initWebSocket, getRecordList } from '/@/hooks/web/useVentWebSocket';
  146. import { deviceControlApi } from '/@/api/vent/index';
  147. import { mountedThree, destroy, addFmText, play, setModelType } from './windrect.threejs';
  148. import LivePlayer from '@liveqing/liveplayer-v3';
  149. import { list, pathList, deviceList, testWind } from './windrect.api';
  150. import { list as baseList } from '../../deviceManager/windfindingTabel/windfinding.api';
  151. import { message, Tag, Progress } from 'ant-design-vue';
  152. import { chartsColumns } from './windrect.data';
  153. const modalTable = ref();
  154. const runNum = ref(5); //设备运行数量
  155. const criticalPathList = ref([]);
  156. const player1 = ref<HTMLDivElement | null>(null);
  157. const player2 = ref<HTMLDivElement | null>(null);
  158. const activeKey = ref('1');
  159. const loading = ref(false);
  160. // 默认初始是第一行
  161. const selectRowIndex = ref(0);
  162. // 监测数据
  163. const selectData = reactive({
  164. deviceID: '',
  165. deviceType: '',
  166. strname: '',
  167. dataDh: '-', //压差
  168. dataDtestq: '-', //测试风量
  169. sourcePressure: '-', //气源压力
  170. dataDequivalarea: '-',
  171. netStatus: '0', //通信状态
  172. fault: '气源压力超限',
  173. });
  174. const flvURL1 = () => {
  175. return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`;
  176. };
  177. const flvURL2 = () => {
  178. return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`;
  179. };
  180. // const dataSource = computed(() => {
  181. // const data = [...getRecordList()] || [];
  182. // Object.assign(selectData, toRaw(data[selectRowIndex.value]));
  183. // addFmText(selectData);
  184. // return data;
  185. // });
  186. const dataSource = ref([]);
  187. const tabChange = (activeKeyVal) => {
  188. activeKey.value = activeKeyVal;
  189. };
  190. // 设备数据
  191. const controlType = ref(1);
  192. // https获取监测数据
  193. let timer: null | NodeJS.Timeout = null;
  194. const getMonitor = () => {
  195. if (Object.prototype.toString.call(timer) === '[object Null]') {
  196. timer = setTimeout(() => {
  197. list({ devicetype: 'windrect', pagetype: 'normal' }).then((res) => {
  198. dataSource.value = res.msgTxt[0].datalist || [];
  199. dataSource.value.forEach((data: any) => {
  200. const readData = data.readData;
  201. data = Object.assign(data, readData);
  202. });
  203. const data: any = toRaw(dataSource.value[selectRowIndex.value]); //maxarea
  204. Object.assign(selectData, data);
  205. addFmText(selectData);
  206. // 根据3个点位分别执行动画
  207. if (timer) {
  208. timer = null;
  209. }
  210. getMonitor();
  211. });
  212. }, 1000);
  213. }
  214. };
  215. // 自测动画方法
  216. const testPlay = (flag) => {
  217. play(flag);
  218. // setTimeout(() => {
  219. // play('up')
  220. // }, 0)
  221. // setTimeout(() => {
  222. // play('center')
  223. // }, 10000)
  224. // setTimeout(() => {
  225. // play('down')
  226. // }, 40000)
  227. // setTimeout(() => {
  228. // play('up')
  229. // }, 60000)
  230. };
  231. // 切换检测数据
  232. const getSelectRow = async (selectRow, index) => {
  233. loading.value = true;
  234. selectRowIndex.value = index;
  235. // Object.assign(selectData, selectRow);
  236. const type = selectRowIndex.value < 3 ? 'lmWindRect' : selectRowIndex.value < 6 ? 'zdWindRect' : 'dsWindRect';
  237. await setModelType(type);
  238. loading.value = false;
  239. };
  240. const start = (flag) => {
  241. const data = {
  242. deviceid: selectData.deviceID,
  243. devicetype: selectData.deviceType,
  244. paramcode: flag == 1 ? 'testStart' : '',
  245. };
  246. deviceControlApi(data).then((res) => {
  247. if (res.success) {
  248. //
  249. }
  250. });
  251. };
  252. const addPlayVideo = (event) => {
  253. event.preventDefault();
  254. if (player1.value.play && player2.value.play) {
  255. player1.value.play();
  256. player2.value.play();
  257. document.body.removeEventListener('mousedown', addPlayVideo);
  258. }
  259. };
  260. //表单赋值
  261. const [registerModal, { setModalProps, closeModal }] = useModalInner();
  262. /* 一键测风 */
  263. const handleOk = () => {
  264. const ids = toRaw(modalTable.value.selectedRowKeys);
  265. testWind({ ids: ids, maxnum: runNum.value }).then((res) => {
  266. message.success(res);
  267. setModalProps({ visible: false });
  268. modalTable.value.clearSelectedRowKeys();
  269. });
  270. };
  271. /* 打开一键测风弹窗 */
  272. const openModel = () => {
  273. setModalProps({ visible: true });
  274. };
  275. /* 关闭一键测风弹窗 */
  276. const handleCancel = () => {
  277. setModalProps({ visible: false });
  278. modalTable.value.clearSelectedRowKeys();
  279. };
  280. const getPathList = async () => {
  281. const pathArr = await pathList({});
  282. criticalPathList.value = pathArr.records.filter((item) => {
  283. return item.strsystype == 3;
  284. });
  285. };
  286. /* 根据路线选择测风装置 */
  287. const selectCriticalPath = (pathId) => {
  288. deviceList({ deviceType: 'wind', sysId: pathId }).then((res) => {
  289. const ids: string[] = [];
  290. res.records.forEach((item) => {
  291. ids.push(item.id);
  292. });
  293. if (modalTable.value) modalTable.value.setSelectedRowKeys(ids);
  294. });
  295. };
  296. onBeforeMount(() => {
  297. // const sendVal = JSON.stringify({ pagetype: 'normal', devicetype: 'windrect', orgcode: '', ids: '', systemID: '' });
  298. // initWebSocket(sendVal);
  299. getPathList();
  300. });
  301. onMounted(() => {
  302. loading.value = true;
  303. mountedThree(player1.value, player2.value).then(async () => {
  304. getMonitor();
  305. // loading.value = false;
  306. });
  307. document.body.addEventListener('mousedown', addPlayVideo, false);
  308. });
  309. onUnmounted(() => {
  310. destroy();
  311. if (timer) {
  312. clearTimeout(timer);
  313. timer = undefined;
  314. }
  315. });
  316. </script>
  317. <style scoped lang="less">
  318. @import '/@/design/vent/modal.less';
  319. :deep(.ant-tabs-tabpane-active) {
  320. overflow: auto;
  321. }
  322. .head-line {
  323. display: flex;
  324. flex-direction: row;
  325. justify-content: space-between;
  326. .button-box {
  327. position: relative;
  328. padding: 5px;
  329. border: 1px transparent solid;
  330. border-radius: 5px;
  331. margin-left: 8px;
  332. margin-right: 8px;
  333. width: auto;
  334. height: 34px;
  335. border: 1px solid #65dbea;
  336. display: flex;
  337. align-items: center;
  338. justify-content: center;
  339. color: #fff;
  340. padding: 0 15px;
  341. cursor: pointer;
  342. pointer-events: auto;
  343. &:hover {
  344. background: linear-gradient(#3eb2ff55, #00c3ff55);
  345. }
  346. &::before {
  347. width: calc(100% - 6px);
  348. height: 26px;
  349. content: '';
  350. position: absolute;
  351. top: 3px;
  352. right: 0;
  353. left: 3px;
  354. bottom: 0;
  355. z-index: -1;
  356. border-radius: inherit; /*important*/
  357. background: linear-gradient(#014978, #3bf3fc);
  358. }
  359. }
  360. }
  361. .ant-tabs-tabpane {
  362. overflow-y: auto !important;
  363. }
  364. </style>