index.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. <template>
  2. <component ref="modelRef" :loading="loading" :is="modelComponent" />
  3. <div class="scene-box">
  4. <div class="top-box">
  5. <div class="top-center" style="display: flex">
  6. <div class="row" v-if="Number(selectData.nwindownum) == 2">
  7. <div v-if="hasPermission('window:AreaControl')" class="button-box" @click="setArea(1)">设定前窗面积</div>
  8. <div v-if="hasPermission('window:AreaControl')" class="button-box" @click="setArea(2)">设定后窗面积</div>
  9. <div v-if="hasPermission('window:showAngle')" class="button-box" @click="setAngle(1)">设定前窗角度</div>
  10. <div v-if="hasPermission('window:showAngle')" class="button-box" @click="setAngle(2)">设定后窗角度</div>
  11. <div v-if="hasPermission('window:twoAreaControl')" class="button-box" @click="setControl('1', '窗1面积设定')">窗1面积设定</div>
  12. <div v-if="hasPermission('window:twoAreaControl')" class="button-box" @click="setControl('2', '窗2面积设定')">窗2面积设定</div>
  13. <div v-if="hasPermission('window:twoAngleControl')" class="button-box" @click="setControl('1', '窗1角度设定')">窗1角度设定</div>
  14. <div v-if="hasPermission('window:twoAngleControl')" class="button-box" @click="setControl('2', '窗2角度设定')">窗2角度设定</div>
  15. <!-- 展会功能 -->
  16. <!-- <div v-if="hasPermission('window:ldkz')" class="button-box" @click="setArea(3)">自主联动控制开启</div>
  17. <div v-if="hasPermission('window:ldkz')" class="button-box" @click="setArea(4)">自主联动控制停止</div> -->
  18. </div>
  19. <div class="row" v-if="hasPermission('window:fourAreaControl') && Number(selectData.nwindownum) == 4">
  20. <div class="button-box" @click="setControl('frontSetValue1', '前窗1面积设置')">前窗1面积</div>
  21. <div class="button-box" @click="setControl('frontSetValue2', '前窗2面积设置')">前窗2面积</div>
  22. <div class="button-box" @click="setControl('frontSetValue3', '后窗1面积设置')">后窗1面积</div>
  23. <div class="button-box" @click="setControl('frontSetValue4', '后窗2面积设置')">后窗2面积</div>
  24. </div>
  25. <div class="row" v-if="hasPermission('window:threeAreaControl') && Number(selectData.nwindownum) == 3">
  26. <div class="button-box" @click="setControl('frontSetValue', '前窗面积设置')">前窗面积设定</div>
  27. <div class="button-box" @click="setControl('middleSetValue', '中间窗面积设置')">中间窗面积设定</div>
  28. <div class="button-box" @click="setControl('rearSetValue', '后窗面积设置')">后窗面积设定</div>
  29. </div>
  30. <div class="row" v-if="Number(selectData.nwindownum) == 1">
  31. <div v-if="hasPermission('window:AreaControl')" class="button-box" @click="setArea(1)">设定风窗面积</div>
  32. <div v-if="hasPermission('window:showAngle')" class="button-box" @click="setAngle(1)">设定风窗角度</div>
  33. <div v-if="hasPermission('window:showAngleArea')" class="button-box" @click="setAngle(1)">设定风窗面积</div>
  34. <div v-if="hasPermission('window:showAddArea')" class="button-box" @click="setControl('addSetValue', '增加面积设置')">增加面积</div>
  35. <div v-if="hasPermission('window:showAddArea')" class="button-box" @click="setControl('deSetValue', '减少面积设置')">减少面积</div>
  36. </div>
  37. <div class="row" v-if="hasPermission('window:gateControl') && Number(selectData.ndoorcount) == 2">
  38. <div class="button-box" @click="setControl('frontGateOpen_S', '打开前门')">打开前门</div>
  39. <div class="button-box" @click="setControl('frontGateClose_S', '关闭前门')">关闭前门</div>
  40. <div class="button-box" @click="setControl('rearGateOpen_S', '打开后门')">打开前门</div>
  41. <div class="button-box" @click="setControl('rearGateClose_S', '关闭后门')">关闭前门</div>
  42. </div>
  43. <div class="row" v-if="hasPermission('window:gateControl') && Number(selectData.ndoorcount) == 1">
  44. <div class="button-box" @click="setControl('frontGateOpen_S', '打开门')">打开门</div>
  45. <div class="button-box" @click="setControl('frontGateClose_S', '关闭门')">关闭门</div>
  46. </div>
  47. <div v-if="hasPermission('window:sameSet')" class="button-box" @click="setControl('sameSetValue', '风窗面积设置')">设定风窗面积</div>
  48. <div v-if="hasPermission('window:COTest')" class="button-box" @click="setControl('COTest', 'CO调控预测')">CO调控预测</div>
  49. </div>
  50. <div class="top-right row">
  51. <div v-if="hasPermission('window:zhldkz')" class="button-box" @click="setControl('autoRun', '开启自主调控')">开启自主调控</div>
  52. <div v-if="hasPermission('window:zhldkz')" class="button-box" @click="setControl('autoStop', '关闭自主调控')">关闭自主调控</div>
  53. <div v-if="hasPermission('window:ldkz')" class="button-box" @click="setArea(7)">风窗自主调控</div>
  54. <div v-if="hasPermission('window:gasldkz')" class="button-box" @click="setArea('ldkzStart')">瓦斯超限调控开启</div>
  55. <div v-if="hasPermission('window:controlFull')" class="button-box" @click="setArea(5)">一键全开</div>
  56. <div v-if="hasPermission('window:controlFull')" class="button-box" @click="setArea(6)">一键全关</div>
  57. <div class="row" v-if="hasPermission('window:fourFlControl') && Number(selectData.nwindownum) == 4">
  58. <div class="button-box" @click="setControl('air1', '前窗1风量设置')">设定前窗1风量</div>
  59. <div class="button-box" @click="setControl('air2', '前窗2风量设置')">设定前窗2风量</div>
  60. <div class="button-box" @click="setControl('air3', '后窗1风量设置')">设定后窗1风量</div>
  61. <div class="button-box" @click="setControl('air4', '后窗2风量设置')">设定后窗2风量</div>
  62. </div>
  63. <!-- 展会功能 -->
  64. <!-- <div v-if="hasPermission('window:ldkz')" class="button-box" @click="setArea('ldkzStart')">自主联动控制开启</div>
  65. <div v-if="hasPermission('window:ldkz')" class="button-box" @click="setArea('ldkzStop')">自主联动控制停止</div> -->
  66. <!-- <div class="button-box" @click="() => (linkAlarmShow = true)">预警指标</div> -->
  67. <!-- <div class="control-type row">
  68. <div class="control-title">控制模式:</div>
  69. <a-radio-group v-model:value="controlType">
  70. <a-radio :value="1">就地</a-radio>
  71. <a-radio :value="2">远程</a-radio>
  72. </a-radio-group>
  73. </div> -->
  74. <!-- <div class="run-type row">
  75. <div class="control-title">运行状态:</div>
  76. <a-radio-group v-model:value="controlType">
  77. <a-radio :value="1">检修</a-radio>
  78. </a-radio-group>
  79. </div>
  80. <div class="run-state row">
  81. <div class="control-title">网络状态:</div>
  82. <a-radio-group v-model:value="controlType">
  83. <a-radio :value="1">运行</a-radio>
  84. </a-radio-group>
  85. </div> -->
  86. </div>
  87. </div>
  88. <div class="title-text">
  89. {{ selectData.strinstallpos || selectData.strname }}
  90. </div>
  91. <div class="bottom-tabs-box" @mousedown="setDivHeight($event, 50, scroll)">
  92. <dv-border-box8 :dur="5" :style="`padding: 5px; height: ${scroll.y + 120}px`">
  93. <a-tabs class="tabs-box" v-model:activeKey="activeKey" @change="tabChange">
  94. <a-tab-pane key="1" tab="实时监测">
  95. <MonitorTable
  96. v-if="activeKey === '1'"
  97. ref="MonitorDataTable"
  98. :columnsType="deviceType"
  99. :dataSource="dataSource"
  100. @select-row="getSelectRow"
  101. design-scope="window-monitor"
  102. :scroll="{ y: scroll.y - 40 }"
  103. title="风窗监测"
  104. :isShowPagination="true"
  105. :isShowActionColumn="true"
  106. >
  107. <template #filterCell="{ column, record }">
  108. <a-tag v-if="column.dataIndex === 'warnFlag'" :color="record.warnFlag == '0' ? 'green' : 'red'">{{
  109. record.warnFlag == '0' ? '正常' : '报警'
  110. }}</a-tag>
  111. <a-tag v-if="column.dataIndex === 'netStatus'" :color="record.netStatus == '0' ? 'default' : 'green'">{{
  112. record.netStatus == '0' ? '断开' : '连接'
  113. }}</a-tag>
  114. <div v-if="record.nwindownum == 1 && column.dataIndex === 'rearArea'">/</div>
  115. </template>
  116. <template #action="{ record }">
  117. <a v-if="globalConfig?.showReport" class="table-action-link" @click="deviceEdit($event, 'reportInfo', record)">报表录入</a>
  118. <a class="table-action-link" @click="deviceEdit($event, 'deviceInfo', record)">设备编辑</a>
  119. </template>
  120. </MonitorTable>
  121. </a-tab-pane>
  122. <!-- <a-tab-pane key="2" tab="实时曲线图" force-render>
  123. <div class="tab-item" v-if="activeKey === '2'">
  124. <DeviceEcharts
  125. chartsColumnsType="window_chart"
  126. xAxisPropType="strname"
  127. :dataSource="dataSource"
  128. height="100%"
  129. :chartsColumns="chartsColumns"
  130. :device-list-api="baseList"
  131. device-type="window"
  132. />
  133. </div>
  134. </a-tab-pane> -->
  135. <a-tab-pane key="3" tab="历史数据">
  136. <div class="tab-item" v-if="activeKey === '3'">
  137. <HistoryTable :columns-type="deviceType" :device-type="deviceType" designScope="window-history" :scroll="scroll">
  138. <template #filterCell="{ column, record }">
  139. <!-- <a-tag v-if="column.dataIndex === 'warnFlag'" :color="record.warnFlag == '0' ? 'green' : 'red'">{{
  140. record.warnFlag == '0' ? '正常' : '报警'
  141. }}</a-tag>
  142. <a-tag v-if="column.dataIndex === 'netStatus'" :color="record.netStatus == '0' ? 'default' : 'green'">{{
  143. record.netStatus == '0' ? '断开' : '连接'
  144. }}</a-tag> -->
  145. <div v-if="record.nwindownum == 1 && column.dataIndex === 'rearArea'">/</div>
  146. </template>
  147. </HistoryTable>
  148. </div>
  149. </a-tab-pane>
  150. <a-tab-pane key="4" tab="报警历史">
  151. <div class="tab-item" v-if="activeKey === '4'">
  152. <AlarmHistoryTable
  153. columns-type="alarm"
  154. :device-type="deviceType"
  155. :device-list-api="baseList"
  156. designScope="alarm-history"
  157. :scroll="scroll"
  158. />
  159. </div>
  160. </a-tab-pane>
  161. <a-tab-pane key="5" tab="操作历史">
  162. <div class="tab-item" v-if="activeKey === '5'">
  163. <HandlerHistoryTable
  164. columns-type="operator_history"
  165. :device-type="deviceType"
  166. :device-list-api="baseList"
  167. designScope="alarm-history"
  168. :scroll="scroll"
  169. />
  170. </div>
  171. </a-tab-pane>
  172. </a-tabs>
  173. </dv-border-box8>
  174. </div>
  175. </div>
  176. <div
  177. v-if="renderPlayer"
  178. ref="playerRef"
  179. style="
  180. z-index: 1;
  181. position: absolute;
  182. top: 100px;
  183. right: 0px;
  184. width: 100%;
  185. height: 800px;
  186. pointer-events: none;
  187. overflow-y: auto;
  188. flex-direction: column;
  189. "
  190. >
  191. </div>
  192. <LivePlayer
  193. id="fc-player1"
  194. style="height: 220px; width: 300px; position: absolute; top: 0px; z-index: -1"
  195. ref="player1"
  196. :videoUrl="flvURL1()"
  197. muted
  198. loading
  199. autoplay
  200. controls
  201. loop
  202. fluent
  203. />
  204. <HandleModal :modal-is-show="modalIsShow" :modal-title="modalTitle" :modal-type="modalType" @handle-ok="handleOK" @handle-cancel="handleCancel" />
  205. <DeviceBaseInfo @register="regModal" :device-type="deviceType" />
  206. <LinkControlDesModal
  207. :modal-is-show="linkAlarmShow"
  208. :modal-title="modalTitle"
  209. :device-id="selectData.deviceID"
  210. @close="() => (linkAlarmShow = false)"
  211. />
  212. <SupplyAir
  213. :data="currentData"
  214. :targetVolume="targetVolume"
  215. :modal-is-show="showTargetModal"
  216. :modalType="modalType"
  217. @handle-cancel="() => (showTargetModal = false)"
  218. />
  219. <GasSupplyAir
  220. :data="currentData"
  221. :gasVal="ch4"
  222. :modal-is-show="showGasModal"
  223. :modalType="modalType"
  224. :isMock="isMock"
  225. @handle-cancel="() => (showGasModal = false)"
  226. />
  227. </template>
  228. <script setup lang="ts">
  229. import { message } from 'ant-design-vue';
  230. import DeviceEcharts from '../comment/DeviceEcharts.vue';
  231. import { onBeforeMount, ref, onMounted, onUnmounted, reactive, toRaw, shallowRef, nextTick, inject, unref, onBeforeUnmount } from 'vue';
  232. import MonitorTable from '../comment/MonitorTable.vue';
  233. import HistoryTable from '../comment/HistoryTable.vue';
  234. import AlarmHistoryTable from '../comment/AlarmHistoryTable.vue';
  235. import HandlerHistoryTable from '../comment/HandlerHistoryTable.vue';
  236. import HandleModal from './components/modal.vue';
  237. import SupplyAir from './components/supplyAir.vue';
  238. import GasSupplyAir from './components/gasSupplyAir.vue';
  239. import DeviceBaseInfo from '../comment/components/DeviceBaseInfo.vue';
  240. import LinkControlDesModal from '../comment/components/LinkControlDesModal.vue';
  241. import { mountedThree, destroy, addMonitorText, computePlay, setModelType, initCameraCanvas } from './window.threejs';
  242. import { list, getTableList, updateWindowAutoAdjustStatus } from './window.api';
  243. import { list as baseList } from '../../deviceManager/windWindowTabel/ventanalyWindow.api';
  244. import { deviceControlApi } from '/@/api/vent/index';
  245. import lodash from 'lodash';
  246. import { setDivHeight } from '/@/utils/event';
  247. import { BorderBox8 as DvBorderBox8 } from '@kjgl77/datav-vue3';
  248. import { useRouter } from 'vue-router';
  249. import LivePlayer from '@liveqing/liveplayer-v3';
  250. import { useModal } from '/@/components/Modal';
  251. import { useCamera } from '/@/hooks/system/useCamera';
  252. import { usePermission } from '/@/hooks/web/usePermission';
  253. import { useMessage } from '/@/hooks/web/useMessage';
  254. import { useGlobSetting } from '/@/hooks/setting';
  255. import { getModelComponent } from './window.data';
  256. const { hasPermission } = usePermission();
  257. const { sysOrgCode } = useGlobSetting();
  258. const globalConfig = inject('globalConfig');
  259. const modelRef = ref();
  260. /** 模型对应的组件,根据实际情况分为二维三维 */
  261. const modelComponent = shallowRef(getModelComponent(globalConfig.is2DModel));
  262. const { currentRoute } = useRouter();
  263. const MonitorDataTable = ref();
  264. const { createConfirm } = useMessage();
  265. const playerRef = ref();
  266. const scroll = reactive({
  267. y: 230,
  268. });
  269. const isMock = ref(VENT_PARAM['gasControlMock']);
  270. const modalIsShow = ref<boolean>(false); // 是否显示模态框
  271. const linkAlarmShow = ref<boolean>(false); // 是否显示模态框
  272. const modalTitle = ref(''); // 模态框标题显示内容,根据设备操作类型决定
  273. const modalType = ref(''); // 模态框内容显示类型,设备操作类型
  274. const deviceType = ref('window');
  275. const deviceBaseList = ref([]);
  276. const activeKey = ref('1');
  277. const loading = ref(false);
  278. const renderPlayer = ref(true);
  279. const windowAngle = ref(0);
  280. const ch4 = ref(0.6);
  281. const targetVolume = ref(0);
  282. const showTargetModal = ref(false);
  283. const showGasModal = ref(false);
  284. // const rotationParam = {
  285. // frontDeg0: 0, // 前门初始
  286. // frontDeg1: windowAngle.value, // 前门目标
  287. // backDeg0: 0, // 后门初始
  288. // backDeg1: windowAngle.value, // 后门目标
  289. // };
  290. // 默认初始是第一行
  291. const selectRowIndex = ref(-1);
  292. const dataSource = ref([]);
  293. // 设备数据
  294. const controlType = ref(1);
  295. /** 控制类型枚举 */
  296. enum ControlType {
  297. FRONT_AREA = 1, // 前窗面积/角度
  298. REAR_AREA = 2, // 后窗面积/角度
  299. LDKZ_START = 'ldkzStart', // 自主联动控制开启
  300. LDKZ_STOP = 'ldkzStop', // 自主联动控制停止
  301. FULL_OPEN = 5, // 一键全开
  302. FULL_CLOSE = 6, // 一键全关
  303. AIR_VOLUME_AUTO = 7, // 风量自主调控
  304. AIR_VOLUME_AUTO_EXT = 8, // 风量自主调控扩展
  305. SAME_SET_VALUE = 'sameSetValue', // 前后窗同值设置
  306. CO_TEST = 'COTest', // 一氧化碳测试
  307. AUTO_RUN = 'autoRun', // 自动运行
  308. AUTO_STOP = 'autoStop', // 自动停止
  309. }
  310. /** 控制类型-标题映射配置 */
  311. const CONTROL_CONFIG_MAP = {
  312. [ControlType.FRONT_AREA]: (nwindownum: number, isAngle = false) =>
  313. nwindownum === 2 ? (isAngle ? '设定前窗角度' : '设定前窗面积') : isAngle ? '设定风窗角度' : '设定风窗面积',
  314. [ControlType.REAR_AREA]: (nwindownum: number, isAngle = false) =>
  315. nwindownum === 2 ? (isAngle ? '设定后窗角度' : '设定后窗面积') : isAngle ? '设定风窗角度' : '设定风窗面积',
  316. [ControlType.LDKZ_START]: '自主联动控制开启',
  317. [ControlType.LDKZ_STOP]: '自主联动控制停止',
  318. [ControlType.FULL_OPEN]: '一键全开',
  319. [ControlType.FULL_CLOSE]: '一键全关',
  320. [ControlType.AIR_VOLUME_AUTO]: '风量自主调控',
  321. [ControlType.AIR_VOLUME_AUTO_EXT]: '风量自主调控',
  322. } as const;
  323. const flvURL1 = () => {
  324. return `/video/window.mp4`;
  325. };
  326. const [regModal, { openModal }] = useModal();
  327. const { getCamera, removeCamera } = useCamera();
  328. const tabChange = (activeKeyVal) => {
  329. activeKey.value = activeKeyVal;
  330. if (activeKeyVal == 1) {
  331. nextTick(() => {
  332. MonitorDataTable.value.setSelectedRowKeys([selectData.value.deviceID]);
  333. });
  334. }
  335. };
  336. const initData = {
  337. deviceID: '',
  338. deviceType: '',
  339. strname: '',
  340. dataDh: '-', //压差
  341. dataDtestq: '-', //测试风量
  342. sourcePressure: '-', //气源压力
  343. dataDequivalarea: '-',
  344. netStatus: '0', //通信状态
  345. fault: '气源压力超限',
  346. frontArea: '0',
  347. rearArea: '0',
  348. frontRearDifference: '-',
  349. rearPresentValue: '-',
  350. maxarea: 0,
  351. nwindownum: 0,
  352. };
  353. // 监测数据
  354. const selectData = ref(lodash.cloneDeep(initData));
  355. const currentData = ref(initData);
  356. // https获取监测数据
  357. let timer: null | NodeJS.Timeout = null;
  358. const getMonitor = (flag?) => {
  359. if (Object.prototype.toString.call(timer) === '[object Null]') {
  360. timer = setTimeout(
  361. async () => {
  362. const data = await getDataSource();
  363. if (data) {
  364. currentData.value = data;
  365. selectData.value = data;
  366. if (data.maxarea) {
  367. playAnimation(selectData.value, selectData.value.maxarea);
  368. }
  369. if (sysOrgCode != 'smjthllmk') {
  370. addMonitorText(selectData.value);
  371. }
  372. }
  373. if (timer) {
  374. timer = null;
  375. }
  376. getMonitor();
  377. },
  378. flag ? 0 : 2000
  379. );
  380. }
  381. };
  382. const getDataSource = async () => {
  383. const res = await list({ devicetype: deviceType.value, pagetype: 'normal' });
  384. dataSource.value = res.msgTxt[0].datalist || [];
  385. dataSource.value.forEach((data: any) => {
  386. const readData = data.readData;
  387. data = Object.assign(data, readData);
  388. });
  389. if (dataSource.value.length > 0 && selectRowIndex.value == -1) {
  390. // 初始打开页面
  391. if (currentRoute.value && currentRoute.value['query'] && currentRoute.value['query']['id']) {
  392. MonitorDataTable.value.setSelectedRowKeys([currentRoute.value['query']['id']]);
  393. } else {
  394. MonitorDataTable.value.setSelectedRowKeys([dataSource.value[0]['deviceID']]);
  395. }
  396. }
  397. const data: any = toRaw(dataSource.value[selectRowIndex.value]); //maxarea
  398. return data;
  399. };
  400. // 获取设备基本信息列表
  401. const getDeviceBaseList = () => {
  402. getTableList({ pageSize: 1000 }).then((res) => {
  403. deviceBaseList.value = res.records;
  404. });
  405. };
  406. // 切换检测数据
  407. const getSelectRow = async (selectRow, index) => {
  408. if (!selectRow) return;
  409. selectRowIndex.value = index;
  410. loading.value = true;
  411. const baseData: any = deviceBaseList.value.find((baseData: any) => baseData.id === selectRow.deviceID);
  412. selectData.value = Object.assign(initData, selectRow, baseData);
  413. const type = selectData.value.windowModal ? selectData.value.windowModal : selectData.value.nwindownum == 1 ? 'ddFc5' : 'sdFc1';
  414. await setSVGModelType(type);
  415. await setModelType(type);
  416. addMonitorText(selectData.value);
  417. playAnimation(selectRow, selectData.value.maxarea, true);
  418. loading.value = false;
  419. await getCamera(selectRow.deviceID, playerRef, renderPlayer);
  420. };
  421. // 判断前后窗的面积是否发生改变,如果改变则开启动画
  422. const playAnimation = (data, maxarea = 90, isFirst = false) => {
  423. modelRef.value?.animate?.(data, maxarea);
  424. computePlay(data, maxarea, isFirst);
  425. };
  426. function setSVGModelType(type) {
  427. modelComponent.value = getModelComponent(globalConfig.is2DModel, type);
  428. return nextTick();
  429. }
  430. /** 封装通用的模态框打开方法 */
  431. const openControlModal = (
  432. controlType: ControlType | string | number,
  433. customTitle?: string, // 自定义标题
  434. nwindownum?: number, // 风窗数量
  435. isAngle = false // 是否是角度设置
  436. ) => {
  437. // 参数校验:风窗数量(动态标题必填)
  438. const needWindowNum = [ControlType.FRONT_AREA, ControlType.REAR_AREA].includes(controlType as ControlType);
  439. if (needWindowNum && nwindownum === undefined) {
  440. message.warning('打开模态框失败:缺少风窗数量参数');
  441. return;
  442. }
  443. // 优先使用自定义标题,无则从配置读取
  444. let finalTitle = customTitle;
  445. if (!finalTitle) {
  446. const configItem = CONTROL_CONFIG_MAP[controlType as keyof typeof CONTROL_CONFIG_MAP];
  447. if (typeof configItem === 'function') {
  448. finalTitle = configItem(nwindownum!, isAngle);
  449. } else if (configItem) {
  450. finalTitle = configItem;
  451. } else {
  452. message.warning(`未配置控制类型【${controlType}】的标题`);
  453. return;
  454. }
  455. }
  456. // 统一赋值逻辑
  457. modalType.value = String(controlType);
  458. modalTitle.value = finalTitle;
  459. modalIsShow.value = true;
  460. };
  461. // 设置风窗面积,调用封装的模态框方法
  462. const setArea = (flag: ControlType | number) => {
  463. modalType.value = flag + '';
  464. const nwindownum = selectData.value.nwindownum;
  465. // 面积设置(1/2为面积,其他为功能控制)
  466. if ([ControlType.FRONT_AREA, ControlType.REAR_AREA].includes(flag as ControlType)) {
  467. openControlModal(flag, undefined, nwindownum, false);
  468. } else {
  469. if (!globalConfig?.simulatedPassword) {
  470. openControlModal(flag, undefined, nwindownum);
  471. } else {
  472. handleOK('', modalType.value, selectData.value.nwindownum);
  473. }
  474. }
  475. };
  476. // 重构setAngle方法,调用封装的模态框方法
  477. const setAngle = (flag: ControlType | number) => {
  478. modalType.value = flag + '';
  479. const nwindownum = selectData.value.nwindownum;
  480. // 角度设置(1/2为角度,7为风量自主调控,其他为功能控制)
  481. if ([ControlType.FRONT_AREA, ControlType.REAR_AREA].includes(flag as ControlType)) {
  482. openControlModal(flag, undefined, nwindownum, true);
  483. } else if (flag === ControlType.AIR_VOLUME_AUTO) {
  484. openControlModal(flag);
  485. } else {
  486. if (!globalConfig?.simulatedPassword) {
  487. openControlModal(flag);
  488. } else {
  489. handleOK('', modalType.value, selectData.value.nwindownum);
  490. }
  491. }
  492. };
  493. // 重构setControl方法,调用封装的模态框方法
  494. const setControl = (flag: ControlType | string | number, title: string) => {
  495. openControlModal(flag, title);
  496. };
  497. const handleOK = async (passWord, handlerState, value) => {
  498. if (!passWord && !globalConfig?.simulatedPassword) {
  499. message.warning('请输入密码!');
  500. return;
  501. }
  502. let data = {
  503. deviceid: selectData.value.deviceID,
  504. devicetype: selectData.value.deviceType,
  505. paramcode: '',
  506. password: passWord || globalConfig?.simulatedPassword,
  507. value: null,
  508. };
  509. let params;
  510. // 风窗风量自主调控
  511. if (handlerState == ControlType.AIR_VOLUME_AUTO || handlerState == ControlType.AIR_VOLUME_AUTO_EXT) {
  512. if (handlerState == ControlType.AIR_VOLUME_AUTO) {
  513. // 单道风窗
  514. params = {
  515. windowId: selectData.value.deviceID,
  516. auto: 1,
  517. fengliangF: value,
  518. };
  519. }
  520. updateWindowAutoAdjustStatus(params).then((res) => {
  521. if (res.success) {
  522. if (globalConfig.History_Type == 'remote') {
  523. message.success('指令已下发至生产管控平台成功!');
  524. } else {
  525. message.success('指令已下发成功!');
  526. }
  527. handleCancel();
  528. targetVolume.value = value;
  529. showTargetModal.value = true;
  530. } else {
  531. message.error(res.message);
  532. }
  533. });
  534. return;
  535. } else if (handlerState == ControlType.LDKZ_START || handlerState == ControlType.LDKZ_STOP) {
  536. data.paramcode = ControlType.AUTO_RUN;
  537. data.value = handlerState == ControlType.LDKZ_START ? 1 : 0;
  538. if (handlerState == ControlType.LDKZ_START) {
  539. ch4.value = value;
  540. params = { auto: 1, windowId: selectData.value.deviceID, gasMax: ch4.value };
  541. } else {
  542. params = { auto: 0, windowId: selectData.value.deviceID };
  543. }
  544. if (isMock.value) {
  545. showGasModal.value = true;
  546. } else {
  547. updateWindowAutoAdjustStatus(params).then((res) => {
  548. if (res.success) {
  549. if (globalConfig.History_Type == 'remote') {
  550. message.success('指令已下发至生产管控平台成功!');
  551. } else {
  552. message.success('指令已下发成功!');
  553. }
  554. handleCancel();
  555. showGasModal.value = true;
  556. } else {
  557. message.error(res.message);
  558. }
  559. });
  560. }
  561. } else if (handlerState == ControlType.SAME_SET_VALUE) {
  562. const data1 = {
  563. deviceid: selectData.value.deviceID,
  564. devicetype: selectData.value.deviceType,
  565. paramcode: 'frontSetValue',
  566. password: passWord || globalConfig?.simulatedPassword,
  567. value: value ? value : null,
  568. };
  569. const data2 = {
  570. deviceid: selectData.value.deviceID,
  571. devicetype: selectData.value.deviceType,
  572. paramcode: 'rearSetValue',
  573. password: passWord || globalConfig?.simulatedPassword,
  574. value: value ? value : null,
  575. };
  576. const res1 = await deviceControlApi(data1);
  577. const res2 = await deviceControlApi(data2);
  578. if (res1.success && res2.success) {
  579. message.success('指令已下发成功!');
  580. } else {
  581. message.error(res1.message);
  582. }
  583. handleCancel();
  584. } else {
  585. if (handlerState == ControlType.FRONT_AREA || handlerState == ControlType.REAR_AREA) {
  586. windowAngle.value = value;
  587. data.paramcode = handlerState == ControlType.FRONT_AREA ? 'frontSetValue' : 'rearSetValue';
  588. data.value = windowAngle.value;
  589. } else if (handlerState == ControlType.FULL_OPEN || handlerState == ControlType.FULL_CLOSE) {
  590. data.paramcode = 'frontSetValue';
  591. data.value = handlerState == ControlType.FULL_OPEN ? selectData.value.maxarea : 0;
  592. } else if (handlerState.startsWith('frontSetValue')) {
  593. data.paramcode = handlerState;
  594. data.value = value;
  595. } else if (handlerState == ControlType.CO_TEST) {
  596. if (value > 50) {
  597. // message.warning(`一氧化碳超限,可能存在火灾隐患,是否开启${selectData.value.strinstallpos},使有毒烟气短路?`, 3000);
  598. createConfirm({
  599. iconType: 'warning',
  600. title: '控制',
  601. content: `一氧化碳超限,可能存在火灾隐患,是否开启${selectData.value.strinstallpos},使有毒烟气短路?`,
  602. onOk: () => {
  603. message.success('指令已下发成功!');
  604. handleCancel();
  605. },
  606. onCancel: () => handleCancel(),
  607. });
  608. return;
  609. } else {
  610. message.success('一氧化碳在正常范围内,无需操作!');
  611. handleCancel();
  612. return;
  613. }
  614. } else {
  615. data.paramcode = handlerState;
  616. if (handlerState == ControlType.AUTO_RUN || handlerState == ControlType.AUTO_STOP) {
  617. data.value = null;
  618. } else {
  619. data.value = value;
  620. }
  621. }
  622. deviceControlApi(handlerState == 3 ? params : data)
  623. .then((res) => {
  624. if (res.success) {
  625. if (globalConfig.History_Type == 'remote') {
  626. message.success('指令已下发至生产管控平台成功!');
  627. } else {
  628. message.success('指令已下发成功!');
  629. }
  630. } else {
  631. message.error(res.message);
  632. }
  633. })
  634. .finally(() => {
  635. handleCancel();
  636. });
  637. }
  638. };
  639. const handleCancel = () => {
  640. modalIsShow.value = false;
  641. modalTitle.value = '';
  642. modalType.value = '';
  643. };
  644. function deviceEdit(e: Event, type: string, record) {
  645. e.stopPropagation();
  646. openModal(true, {
  647. type,
  648. deviceId: record['deviceID'],
  649. });
  650. }
  651. onBeforeMount(() => {
  652. getDeviceBaseList();
  653. });
  654. onMounted(() => {
  655. const { query } = unref(currentRoute);
  656. if (query['deviceType']) deviceType.value = query['deviceType'] as string;
  657. if (globalConfig.is2DModel) {
  658. getMonitor(true);
  659. } else {
  660. loading.value = true;
  661. mountedThree().then(async () => {
  662. getMonitor(true);
  663. loading.value = false;
  664. addMonitorText(selectData.value);
  665. });
  666. }
  667. });
  668. onBeforeUnmount(() => {
  669. removeCamera(playerRef);
  670. });
  671. onUnmounted(() => {
  672. destroy();
  673. if (timer) {
  674. clearTimeout(timer);
  675. timer = undefined;
  676. }
  677. });
  678. </script>
  679. <style lang="less" scoped>
  680. @import '/@/design/theme.less';
  681. @import '/@/design/vent/modal.less';
  682. @ventSpace: zxm;
  683. // :deep(.@{ventSpace}-tabs-tabpane-active) {
  684. // height: 100%;
  685. // }
  686. .input-box {
  687. display: flex;
  688. align-items: center;
  689. padding-left: 10px;
  690. .input-title {
  691. color: var(--vent-font-action-link);
  692. width: auto;
  693. }
  694. .@{ventSpace}-input-number {
  695. border-color: #ffffff88 !important;
  696. }
  697. margin-right: 10px;
  698. }
  699. .scene-box {
  700. .bottom-tabs-box {
  701. height: 350px;
  702. }
  703. }
  704. :deep(.@{ventSpace}-picker-datetime-panel) {
  705. height: 200px !important;
  706. overflow-y: auto !important;
  707. }
  708. </style>