index.vue 77 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109
  1. <template>
  2. <component
  3. ref="modelRef"
  4. :loading="loading"
  5. :is="modelComponent"
  6. :selectData="selectData"
  7. :modalType="modalType"
  8. :fanDualArray="fanDualArray"
  9. @go-detail-device="goDetailDevice"
  10. />
  11. <div class="scene-box">
  12. <div class="top-box" v-if="!loading">
  13. <div class="top-center row">
  14. <div
  15. class="vent-flex-row"
  16. id="fanLocalSelectDom"
  17. v-if="!globalConfig?.simulatedPassword && getDictItemsByCode(CONSTS.DICT_CODE_FANLOCALTYPE)"
  18. >
  19. <span style="color: var(--vent-font-color); margin-left: 5px">风机类型:</span>
  20. <JDictSelectTag
  21. style="width: 180px"
  22. v-model:value="devicekind"
  23. :dictCode="CONSTS.DICT_CODE_FANLOCALTYPE"
  24. :showChooseOption="false"
  25. :getPopupContainer="getPopupContainer"
  26. @change="changeDeviceKind"
  27. />
  28. </div>
  29. <!-- fanlocal_systeml_zj 模拟局部风机,不显示操作按钮 -->
  30. <template v-for="(item, index) in modalTypeArr.leftBtnArr" :key="index">
  31. <template v-if="hasPermission(item.permission)">
  32. <div :class="{ 'button-box': btnClick, 'button-disable': !btnClick }" @click="showModal(item)">{{ item.value }} </div>
  33. </template>
  34. </template>
  35. </div>
  36. <div class="top-right row">
  37. <template v-for="(item, index) in modalTypeArr.rightBtnArr" :key="index">
  38. <div v-if="hasPermission(item.permission)" :class="{ 'button-box': btnClick, 'button-disable': !btnClick }" @click="showModal(item)">{{
  39. item.value
  40. }}</div>
  41. <!-- <div
  42. v-if="
  43. item.permission === 'fanLocal:gasAlarmSet' ||
  44. item.permission === 'fanLocal:kkjc' ||
  45. item.permission === 'fanLocal:supplyAir' ||
  46. item.permission === 'fanLocal:dischargeGas'
  47. "
  48. :class="{ 'button-box': btnClick, 'button-disable': !btnClick }"
  49. @click="showModal(item)"
  50. >{{ item.value }}</div
  51. > -->
  52. </template>
  53. </div>
  54. </div>
  55. <div class="title-text">
  56. {{ selectData.supplyAirAddr || selectData.strinstallpos || selectData.stationname }}
  57. </div>
  58. <div class="data-show-box" v-if="!loading">
  59. <div class="data-item" v-if="leftColumns.length > 0">
  60. <div class="item-header">环境监测与评价分析</div>
  61. <div class="item-container">
  62. <div class="tab">
  63. <div class="tab-item" :class="{ 'tab-item-active-r': warningMonitorRowIndex === 0 }" @click="selectDevice('warningMonitorRowIndex', 0)">{{
  64. fanTitles[0]
  65. }}</div>
  66. <div
  67. v-if="isShowStandbyFan"
  68. class="tab-item"
  69. :class="{ 'tab-item-active-r': warningMonitorRowIndex === 1 }"
  70. @click="selectDevice('warningMonitorRowIndex', 1)"
  71. >{{ fanTitles[1] }}</div
  72. >
  73. </div>
  74. <div class="container-group container-group-l">
  75. <div class="warning-group" :style="computedStyle">
  76. <template v-if="selectData.deviceType">
  77. <div class="container-item" v-for="(data, index) in leftColumns" :key="index">
  78. <div class="item-icon">
  79. <!-- <SvgIcon class="icon-style" size="18" name="temperature" /> -->
  80. <CaretRightOutlined class="icon-style" />
  81. </div>
  82. <div class="item-name">{{ data.title }}</div>
  83. <div v-if="data.dataIndex.startsWith('Fan')">
  84. <div class="item-value" v-if="warningMonitorRowIndex == 0">{{ getFanValue(data.dataIndex, 'Fan1') }}</div>
  85. <div class="item-value" v-if="warningMonitorRowIndex == 1">{{ getFanValue(data.dataIndex, 'Fan2') }}</div>
  86. </div>
  87. <div v-else>
  88. <div class="item-value">{{ selectData[data.dataIndex] ? selectData[data.dataIndex] : '-' }}</div>
  89. </div>
  90. </div>
  91. </template>
  92. </div>
  93. </div>
  94. <div class="container-group" v-if="hasPermission(CONSTS.PERMISSIONS.FAN_LOCAL_PJFX)">
  95. <div class="warning-header">
  96. <div class="header-item">
  97. <div class="header-title">评价分析</div>
  98. </div>
  99. </div>
  100. <div class="warning-group" style="max-height: 200px; overflow-y: auto">
  101. <template v-if="selectData.deviceType && globSetting.sysOrgCode != 'sdtljtdhzmk' && globSetting.sysOrgCode != 'hnjtymhmk'">
  102. <div class="container-item" v-for="(data, index) in leftColumns1" :key="index">
  103. <div class="item-icon">
  104. <!-- <SvgIcon class="icon-style" size="18" name="temperature" /> -->
  105. <CaretRightOutlined class="icon-style" />
  106. </div>
  107. <div class="item-name">{{ data.title }}</div>
  108. <div :class="['item-value', { 'text-red-bold': data.value === '超限' }]">{{ data.value ? data.value : '-' }}</div>
  109. </div>
  110. </template>
  111. <template v-else-if="selectData.deviceType && (globSetting.sysOrgCode === 'sdtljtdhzmk' || globSetting.sysOrgCode == 'hnjtymhmk')">
  112. <div class="container-item" v-for="(data, index) in leftColumns2" :key="index">
  113. <div class="item-icon">
  114. <!-- <SvgIcon class="icon-style" size="18" name="temperature" /> -->
  115. <CaretRightOutlined class="icon-style" />
  116. </div>
  117. <div class="item-name">{{ data.title }}</div>
  118. <div :class="['item-value', { 'text-red-bold': data.value === '超限' }]">{{ data.value ? data.value : '-' }}</div>
  119. </div>
  120. </template>
  121. </div>
  122. </div>
  123. </div>
  124. </div>
  125. <div>
  126. <div class="data-item" v-if="rightColumns.length > 0">
  127. <div class="item-header">设备故障诊断与预警</div>
  128. <div class="item-container">
  129. <div class="tab">
  130. <div class="tab-item" :class="{ 'tab-item-active-r': dataMonitorRowIndex == 0 }" @click="selectDevice('dataMonitorRowIndex', 0)">{{
  131. fanTitles[0]
  132. }}</div>
  133. <div
  134. v-if="isShowStandbyFan"
  135. class="tab-item"
  136. :class="{ 'tab-item-active-r': dataMonitorRowIndex == 1 }"
  137. @click="selectDevice('dataMonitorRowIndex', 1)"
  138. >{{ fanTitles[1] }}</div
  139. >
  140. </div>
  141. <div class="container-group container-group-l">
  142. <div class="warning-group" style="max-height: 200px; overflow-y: auto">
  143. <template v-if="deviceType">
  144. <div v-for="(state, index) in rightColumns" :key="index">
  145. <template v-if="!state.dataIndex.endsWith('_w')">
  146. <div class="container-item">
  147. <div class="item-icon">
  148. <CaretRightOutlined class="icon-style" />
  149. </div>
  150. <div class="item-name">{{ state.title }}</div>
  151. <div v-if="state.dataIndex.startsWith('Fan')">
  152. <div class="item-value" v-if="dataMonitorRowIndex == 0">{{ getFanValue(state.dataIndex, 'Fan1') }}</div>
  153. <div class="item-value" v-if="dataMonitorRowIndex == 1">{{ getFanValue(state.dataIndex, 'Fan2') }}</div>
  154. </div>
  155. <div v-else>
  156. <div class="item-value">{{ selectData[state.dataIndex] ? selectData[state.dataIndex] : '-' }} </div>
  157. </div>
  158. </div>
  159. </template>
  160. </div>
  161. </template>
  162. </div>
  163. <div class="warning-group">
  164. <div class="warning-header">
  165. <div class="header-item">
  166. <div class="header-title">设备报警</div>
  167. <!-- <div class="header-value">{{ selectData['warnLogNotOkCount'] }} </div> -->
  168. </div>
  169. </div>
  170. <template v-if="deviceType">
  171. <div v-if="rightColumns.length > 1 && rightColumns.find((item) => item.dataIndex?.endsWith('_w'))">
  172. <div v-for="(state, index) in rightColumns" :key="index">
  173. <template v-if="state.dataIndex.endsWith('_w')">
  174. <div class="warning-item">
  175. <div class="item-name"> <div class="icon"></div> {{ state.title }} </div>
  176. <div v-if="state.dataIndex.startsWith('Fan')">
  177. <div class="signal-item" v-if="warningMonitorRowIndex == 0">
  178. <div
  179. class="signal-round"
  180. :class="{
  181. 'signal-round-run': getFanValue(state.dataIndex, 'Fan1') == '0',
  182. 'signal-round-warning':
  183. getFanValue(state.dataIndex, 'Fan1') !== undefined && getFanValue(state.dataIndex, 'Fan1') == '1',
  184. 'signal-round-gry': getFanValue(state.dataIndex, 'Fan1') === undefined,
  185. }"
  186. ></div>
  187. <div class="vent-margin-l-8">{{
  188. getFanValue(state.dataIndex, 'Fan1') === undefined
  189. ? '无状态'
  190. : getFanValue(state.dataIndex, 'Fan1') == '0'
  191. ? '正常'
  192. : '异常'
  193. }}</div>
  194. </div>
  195. <div class="signal-item" v-if="warningMonitorRowIndex == 1">
  196. <div
  197. class="signal-round"
  198. :class="{
  199. 'signal-round-run': getFanValue(state.dataIndex, 'Fan2') == '0',
  200. 'signal-round-warning':
  201. getFanValue(state.dataIndex, 'Fan2') != undefined && getFanValue(state.dataIndex, 'Fan2') == '1',
  202. 'signal-round-gry': getFanValue(state.dataIndex, 'Fan2') == undefined,
  203. }"
  204. ></div>
  205. <div class="vent-margin-l-8">{{
  206. getFanValue(state.dataIndex, 'Fan2') == undefined
  207. ? '无状态'
  208. : getFanValue(state.dataIndex, 'Fan2') == '0'
  209. ? '正常'
  210. : '异常'
  211. }}</div>
  212. </div>
  213. </div>
  214. <div v-else>
  215. <div class="signal-item">
  216. <div
  217. class="signal-round vent-margin-l-8"
  218. :class="{
  219. 'signal-round-run': selectData[state.dataIndex] == '0',
  220. 'signal-round-warning': selectData[state.dataIndex] !== undefined && selectData[state.dataIndex] == '1',
  221. 'signal-round-gry': selectData[state.dataIndex] === undefined,
  222. }"
  223. ></div>
  224. <div class="vent-margin-l-8">{{
  225. selectData[state.dataIndex] === undefined ? '无状态' : selectData[state.dataIndex] == '0' ? '正常' : '异常'
  226. }}</div>
  227. </div>
  228. </div>
  229. </div>
  230. </template>
  231. </div>
  232. </div>
  233. <div v-else style="height: 200px; overflow-y: auto; text-align: center; color: #fff; line-height: 200px"> 暂无报警 </div>
  234. </template>
  235. </div>
  236. </div>
  237. </div>
  238. </div>
  239. </div>
  240. </div>
  241. <div class="bottom-tabs-box" @mousedown="setDivHeight($event, 175, scroll, 0)">
  242. <dv-border-box8 :dur="5" class="dv_border_8" :style="`bottom: 20px; padding: 5px; height: ${scroll.y + 140}px`">
  243. <!-- <div class="enter-detail" @click="goDetail()">
  244. <send-outlined class=""/>风机运行详情
  245. </div> -->
  246. <a-tabs class="tabs-box" v-model:activeKey="activeKey" @change="tabChange">
  247. <a-tab-pane key="1" tab="实时监测">
  248. <GroupMonitorTable
  249. v-if="activeKey === '1'"
  250. ref="MonitorDataTable"
  251. :dataSource="dataSource"
  252. :columnsType="`${selectData.deviceType}_monitor`"
  253. @select-row="getSelectRow"
  254. :scroll="scroll"
  255. :is-action="true"
  256. >
  257. <template #action="{ record }">
  258. <a v-if="globalConfig?.showReport" class="table-action-link" @click="deviceEdit($event, 'reportInfo', record)">报表录入</a>
  259. <a class="table-action-link" @click="deviceEdit($event, 'deviceInfo', record)">设备编辑</a>
  260. </template>
  261. </GroupMonitorTable>
  262. </a-tab-pane>
  263. <a-tab-pane key="3" tab="历史数据">
  264. <div class="tab-item" v-if="activeKey === '3'">
  265. <template v-if="globalConfig.History_Type == 'remote'">
  266. <HistoryTable
  267. :columns-type="`${selectData.deviceType}`"
  268. :device-type="`${devicekind}`"
  269. designScope="fanlocal-history"
  270. :scroll="scroll"
  271. />
  272. </template>
  273. <template v-else>
  274. <HistoryTable class="w-100% h-100%" :device-code="`${devicekind}`" :scroll="scroll" dict-code="fanlocal_dict" />
  275. </template>
  276. </div>
  277. </a-tab-pane>
  278. <a-tab-pane key="7" v-if="hasPermission('show:historyFanLocalEcharts')" tab="历史曲线图" force-render>
  279. <div class="tab-item" v-if="activeKey === '7'">
  280. <FanDeviceEcharts
  281. chartsColumnsType="fanlocal_chart"
  282. xAxisPropType="strname"
  283. :dataSource="dataSource"
  284. height="100%"
  285. width="100%"
  286. :fan1ChartsColumns="chartsColumnsFan1"
  287. :fan2ChartsColumns="chartsColumnsFan2"
  288. :device-list-api="list.bind(null, { devicetype: 'fanlocal', pagetype: 'normal' })"
  289. device-type="fanlocal"
  290. />
  291. </div>
  292. </a-tab-pane>
  293. <a-tab-pane key="4" tab="报警历史">
  294. <div class="tab-item" v-if="activeKey === '4'">
  295. <AlarmHistoryTable columns-type="alarm" :device-type="`${devicekind}`" designScope="alarm-history" :scroll="scroll" />
  296. </div>
  297. </a-tab-pane>
  298. <a-tab-pane key="5" tab="操作历史">
  299. <div class="tab-item" v-if="activeKey === '5'">
  300. <HandlerHistoryTable
  301. columns-type="operator_history"
  302. :device-type="`${devicekind}`"
  303. :device-list-api="baseList"
  304. designScope="alarm-history"
  305. :scroll="scroll"
  306. />
  307. </div>
  308. </a-tab-pane>
  309. <a-tab-pane key="6" tab="风机曲线" v-if="hasPermission('show:fanEcharts')">
  310. <div v-if="activeKey == '6'" class="tab-item" style="display: flex">
  311. <div style="width: calc(50% - 20px); height: 100%">
  312. <div style="color: var(--vent-font-color); width: 100%; text-align: center; margin: 5px 0px">{{ fanTitles[0] }}</div>
  313. <BarAndLine
  314. class="echarts-line"
  315. xAxisPropType="readTime"
  316. :dataSource="echartsDataList"
  317. height="90%"
  318. width="100%"
  319. :chartsColumns="chartsColumnsFan1"
  320. :option="echatsOption"
  321. chartsType="detail"
  322. />
  323. </div>
  324. <div style="width: calc(50% - 20px); height: 100%">
  325. <div style="color: var(--vent-font-color); width: 100%; text-align: center; margin: 5px 0px">备机</div>
  326. <BarAndLine
  327. class="echarts-line"
  328. xAxisPropType="readTime"
  329. :dataSource="echartsDataList"
  330. height="90%"
  331. width="100%"
  332. :chartsColumns="chartsColumnsFan2"
  333. :option="echatsOption"
  334. chartsType="detail"
  335. />
  336. </div>
  337. </div>
  338. </a-tab-pane>
  339. <a-tab-pane key="2" tab="风量实时曲线图" force-render v-if="hasPermission('echart:show')">
  340. <div class="tab-item" style="height: 100%; padding-top: 15px">
  341. <BarAndLine
  342. v-if="activeKey == '2'"
  343. class="echarts-line"
  344. xAxisPropType="time"
  345. :dataSource="historyList"
  346. height="100%"
  347. width="100%"
  348. :chartsColumns="chartsColumnsZDKZ"
  349. :option="echatsOption"
  350. />
  351. </div>
  352. </a-tab-pane>
  353. </a-tabs>
  354. <a-button
  355. v-if="hasPermission(CONSTS.PERMISSIONS.BTN_REPORT_DOWN)"
  356. type="primary"
  357. size="small"
  358. preIcon="ant-design:download-outlined"
  359. style="position: absolute; right: 15px; top: 10px"
  360. @click="reportDown"
  361. >
  362. 报表导出
  363. </a-button>
  364. </dv-border-box8>
  365. </div>
  366. </div>
  367. <div
  368. v-if="renderPlayer"
  369. ref="playerRef"
  370. v-show="showPlay"
  371. :class="{ 'to-right': rightColumns.length < 1 || leftColumns.length < 1, 'to-no-right': rightColumns.length > 0 && leftColumns.length > 0 }"
  372. style="z-index: 999; position: absolute; top: 100px; right: 15px; width: 100%; height: 100%; margin: auto; pointer-events: none"
  373. >
  374. </div>
  375. <a-modal v-model:visible="modalIsShow" :title="modalTitle" :maskStyle="{ backgroundColor: '#000000aa', backdropFilter: 'blur(3px)' }">
  376. <template #footer>
  377. <div v-if="controlType != 'startFan'">
  378. <a-button key="back" @click="cancel">返回</a-button>
  379. <a-button key="submit" type="primary" :loading="loading" @click="handleOk">确定</a-button>
  380. </div>
  381. </template>
  382. <div class="modal-container">
  383. <div class="vent-flex-row">
  384. <ExclamationCircleFilled style="color: #ffb700; font-size: 30px" />
  385. <div class="warning-text">您是否要进行{{ modalTitle }}操作?</div>
  386. </div>
  387. <div class="" v-if="controlType == 'startSmoke'">
  388. <!-- 互斥控制 -->
  389. <div class="startSmoke-select">
  390. <div class="label">主机:</div>
  391. <a-radio-group v-model:value="mainWindIsShow1" @change="changeMotor" name="localWind1">
  392. <a-radio value="open">开启</a-radio>
  393. <a-radio value="stop">停止</a-radio>
  394. </a-radio-group>
  395. </div>
  396. <div v-if="isShowStandbyFan" class="startSmoke-select">
  397. <div class="label">备机:</div>
  398. <a-radio-group v-model:value="mainWindIsShow2" @change="changeMotor" name="localWind2">
  399. <a-radio value="open">开启</a-radio>
  400. <a-radio value="stop">停止</a-radio>
  401. </a-radio-group>
  402. </div>
  403. </div>
  404. <div class="" v-if="controlType == 'startFan'">
  405. <!-- 不互斥控制 -->
  406. <div class="startSmoke-select">
  407. <div class="label">主机:</div>
  408. <div :class="{ 'button-box': true, 'button-disable': false }" @click="handleOk('Fan1Open')">开启</div>
  409. <div :class="{ 'button-box': true, 'button-disable': false }" @click="handleOk('Fan1Stop')">停止</div>
  410. <div :class="{ 'button-box': true, 'button-disable': false }" @click="handleOk('Fan1Reset')">复位</div>
  411. </div>
  412. <div v-if="isShowStandbyFan" class="startSmoke-select">
  413. <div class="label">备机:</div>
  414. <div :class="{ 'button-box': true, 'button-disable': false }" @click="handleOk('Fan2Open')">开启</div>
  415. <div :class="{ 'button-box': true, 'button-disable': false }" @click="handleOk('Fan2Stop')">停止</div>
  416. <div :class="{ 'button-box': true, 'button-disable': false }" @click="handleOk('Fan2Reset')">复位</div>
  417. </div>
  418. </div>
  419. <!-- 远程就地 -->
  420. <div class="" v-if="controlType == 'remote'">
  421. <div class="startSmoke-select">
  422. <div class="label">主机:</div>
  423. <div :class="{ 'button-box': true, 'button-disable': false }" @click="handleOk('Fan1remote')">远程/就地切换</div>
  424. </div>
  425. <div v-if="isShowStandbyFan" class="startSmoke-select">
  426. <div class="label">备机:</div>
  427. <div :class="{ 'button-box': true, 'button-disable': false }" @click="handleOk('Fan2remote')">远程/就地切换</div>
  428. </div>
  429. </div>
  430. <!-- 调频 -->
  431. <div class="vent-flex-row input-box" v-if="controlType == 'Fan1Frequency'">
  432. <div class="label">主风机运行频率(Hz):</div>
  433. <a-input-number
  434. size="small"
  435. v-model:value="fan1FrequencyVal"
  436. :min="CONSTS.FAN_FREQ_MIN"
  437. :max="CONSTS.FAN_FREQ_MAX"
  438. :step="CONSTS.FAN_FREQ_STEP"
  439. />
  440. </div>
  441. <div class="vent-flex-row input-box" v-if="controlType == 'Fan2Frequency'">
  442. <div class="label">备风机运行频率(Hz):</div>
  443. <!-- 优化:使用常量替代硬编码 -->
  444. <a-input-number
  445. size="small"
  446. v-model:value="fan2FrequencyVal"
  447. :min="CONSTS.FAN_FREQ_MIN"
  448. :max="CONSTS.FAN_FREQ_MAX"
  449. :step="CONSTS.FAN_FREQ_STEP"
  450. />
  451. </div>
  452. <div class="vent-flex-row input-box" v-if="controlType == 'FanFrequency'">
  453. <div class="label">风机运行频率(Hz):</div>
  454. <!-- 优化:使用常量替代硬编码 -->
  455. <a-input-number
  456. size="small"
  457. v-model:value="fan1FrequencyVal"
  458. :min="CONSTS.FAN_FREQ_MIN"
  459. :max="CONSTS.FAN_FREQ_MAX"
  460. :step="CONSTS.FAN_FREQ_STEP"
  461. />
  462. </div>
  463. <div class="vent-flex-row input-box" v-if="controlType == 'disAirAlarm'">
  464. <div class="label">漏风率(%):</div>
  465. <a-input-number size="small" v-model:value="frequencyVal" :min="0" :max="50" :step="0.1" />
  466. </div>
  467. <div class="vent-flex-row input-box" v-if="controlType == 'diameter'">
  468. <div class="label">风筒直径(m):</div>
  469. <a-input-number size="small" v-model:value="frequencyVal" :min="0" :max="50" :step="0.1" />
  470. </div>
  471. <div class="vent-flex-row input-box" v-if="controlType == 'len'">
  472. <div class="label">风筒长度(m):</div>
  473. <a-input-number size="small" v-model:value="frequencyVal" :min="0" :max="50" :step="0.1" />
  474. </div>
  475. <div class="vent-flex-row input-box" v-if="controlType == 'windPowerLimit'">
  476. <div class="label">风电闭锁限值(m³/min):</div>
  477. <a-input-number size="small" v-model:value="frequencyVal" :min="0" :max="50" :step="0.1" />
  478. </div>
  479. <div class="vent-flex-row input-box" v-if="controlType == 'gasPowerLimit'">
  480. <div class="label">瓦斯电闭锁限值(m³/min):</div>
  481. <a-input-number size="small" v-model:value="frequencyVal" :min="0" :max="50" :step="0.1" />
  482. </div>
  483. <div v-if="controlType == 'gasAlarmSet'">
  484. <div class="vent-flex-row input-box">
  485. <div class="label">设置瓦斯超限浓度:</div>
  486. <a-input-number size="small" v-model:value="gasWarningVal" :min="0" :max="1" :step="0.01" />
  487. </div>
  488. </div>
  489. <div v-if="controlType == 'gasAlarm'">
  490. <div class="vent-flex-row input-box">
  491. <div class="label">传感器名称:</div>
  492. <a-select placeholder="传感器" @change="handleChangeSensor" :options="sensorList" v-model:value="modalSensor" />
  493. </div>
  494. <div class="vent-flex-row input-box">
  495. <div class="label">传感器值:</div>
  496. <a-input-number size="small" v-model:value="frequencyVal" :min="0" :max="50" :step="0.1" />
  497. </div>
  498. </div>
  499. <div v-if="controlType == 'airVolumeAlarm'">
  500. <div class="vent-flex-row input-box">
  501. <div class="label">风量上限(m³/min):</div>
  502. <a-input-number size="small" v-model:value="modalTypeArr.rightBtnArr[3].min" :min="0" :max="50" :step="0.1" />
  503. </div>
  504. <div class="vent-flex-row input-box">
  505. <div class="label">风量下限(m³/min):</div>
  506. <a-input-number size="small" v-model:value="modalTypeArr.rightBtnArr[3].max" :min="0" :max="50" :step="0.1" />
  507. </div>
  508. </div>
  509. <div class="vent-flex-row input-box" v-if="controlType == 'zhlk'">
  510. <div class="label">目标风量(m³/min):</div>
  511. <a-input-number size="small" v-model:value="targetVolume" />
  512. </div>
  513. <div v-if="controlType == 'gasOverSet'">
  514. <div class="vent-flex-row input-box">
  515. <div class="label">设置瓦斯超限浓度:</div>
  516. <a-input-number size="small" v-model:value="gasWarningVal" :min="0" :max="1" :step="0.01" />
  517. </div>
  518. </div>
  519. <div v-if="controlType == 'dischargeGas'">
  520. <div class="vent-flex-row input-box">
  521. <div class="label">设置瓦斯超限浓度:</div>
  522. <a-input-number size="small" v-model:value="gasWarningVal1" :min="0" :max="0.8" :step="0.01" />
  523. </div>
  524. </div>
  525. <div v-if="controlType == 'supplyAir'">
  526. <div class="vent-flex-row input-box">
  527. <div class="label">设置目标风量:</div>
  528. <a-input-number size="small" v-model:value="targetVolume1" :min="0" :max="100000" :step="100" />
  529. </div>
  530. </div>
  531. <!-- 启动或停止 -->
  532. <div class="" v-if="controlType == 'startSmoke'"> </div>
  533. <div v-if="!globalConfig?.simulatedPassword" class="vent-flex-row input-box">
  534. <div class="label">操作密码:</div>
  535. <a-input size="small" type="password" v-model:value="passWord" />
  536. </div>
  537. </div>
  538. </a-modal>
  539. <!-- 摄像头显示隐藏图标 -->
  540. <VideoCameraOutlined class="video-icon" :class="{ 'no-play': !showPlay }" @click="changePlay" />
  541. <ConditionAssistance @register="registerModalAssistance" :dataSource="historySource" />
  542. <SupplyAir @register="registerModal2" :data="currentDeviceData" :targetVolume="targetVolume1" :fanlocalId="selectData.deviceID" />
  543. <DischargeGas @register="registerModal3" :data="currentDeviceData" :gasMax="gasWarningVal1" :fanlocalId="selectData.deviceID" />
  544. <DeviceBaseInfo @register="registerModal" :device-type="selectData['deviceType']" />
  545. <reportInfo @register="registerModal1" :editID="editID" :fileType="fileType" />
  546. </template>
  547. <script setup lang="ts">
  548. import { ExclamationCircleFilled, VideoCameraOutlined } from '@ant-design/icons-vue';
  549. import {
  550. onBeforeMount,
  551. ref,
  552. watch,
  553. onMounted,
  554. nextTick,
  555. defineAsyncComponent,
  556. reactive,
  557. onUnmounted,
  558. inject,
  559. unref,
  560. computed,
  561. shallowReactive,
  562. shallowRef,
  563. onBeforeUnmount,
  564. readonly,
  565. } from 'vue';
  566. import FanDeviceEcharts from '../comment/FanDeviceEcharts.vue';
  567. import BarAndLine from '../../../../components/chart/BarAndLine.vue';
  568. import GroupMonitorTable from '../comment/GroupMonitorTable.vue';
  569. import AlarmHistoryTable from '../comment/AlarmHistoryTable.vue';
  570. import HandlerHistoryTable from '../comment/HandlerHistoryTable.vue';
  571. import DeviceBaseInfo from '../comment/components/DeviceBaseInfo.vue';
  572. import { mountedThree, setModelType, destroy, addCssText, addText, playSmoke } from './fanLocal.threejs';
  573. import lodash from 'lodash';
  574. import { getTableList, list, autoAdjust } from '/@/views/vent/monitorManager/fanLocalMonitor/fanLocal.api';
  575. import { list as baseList } from '../../deviceManager/fanTabel/fan.api';
  576. import { echatsOption, chartsColumnsZDKZ, getModelComponent } from './fanLocal.data';
  577. import { deviceControlApi } from '/@/api/vent/index';
  578. import { setDivHeight } from '/@/utils/event';
  579. import { BorderBox8 as DvBorderBox8 } from '@kjgl77/datav-vue3';
  580. import { getTableHeaderColumns } from '/@/hooks/web/useWebColumns';
  581. import { useRouter } from 'vue-router';
  582. import { useModal } from '/@/components/Modal';
  583. import type { BasicColumn } from '/@/components/Table/src/types/table';
  584. import JDictSelectTag from '/@/components/Form/src/jeecg/components/JDictSelectTag.vue';
  585. import { getPopupContainer } from '/@/utils';
  586. import { getDictItemsByCode } from '/@/utils/dict';
  587. import { message } from 'ant-design-vue';
  588. import { useCamera } from '/@/hooks/system/useCamera';
  589. import { CaretRightOutlined } from '@ant-design/icons-vue';
  590. import ConditionAssistance from './components/conditionAssistance.vue';
  591. import DischargeGas from './components/dischargeGas.vue';
  592. import SupplyAir from './components/supplyAir.vue';
  593. import reportInfo from '../comment/components/reportInfo.vue';
  594. import { save, reportList } from '../../reportManager/reportManager.api';
  595. import { usePermission } from '/@/hooks/web/usePermission';
  596. import { useGlobSetting } from '/@/hooks/setting';
  597. import { useMessage } from '/@/hooks/web/useMessage';
  598. import { cloneDeep } from 'lodash-es';
  599. // 优化:提取硬编码为模块级常量
  600. const CONSTS = {
  601. // 频率相关常量
  602. FAN_FREQ_MIN: 20,
  603. FAN_FREQ_MAX: 50,
  604. FAN_FREQ_STEP: 0.1,
  605. // 瓦斯报警值常量
  606. GAS_WARNING_VAL_DEFAULT: 0.6,
  607. GAS_WARNING_VAL1_DEFAULT: 0.8,
  608. // 设备类型/字典常量
  609. DEVICE_TYPE_FANLOCAL: 'fanlocal',
  610. DICT_CODE_FANLOCALTYPE: 'fanlocaltype',
  611. // 风机名称常量
  612. FAN_NAME_MAIN: '主机',
  613. FAN_NAME_STANDBY: '备机',
  614. // 滚动高度默认值
  615. SCROLL_Y_DEFAULT: 180,
  616. // 权限码常量(统一管理,避免拼写错误)
  617. PERMISSIONS: {
  618. FAN_LOCAL_PJFX: 'fanLocal:pjfx',
  619. FAN_LOCAL_SHOW_CAMERA: 'fanlocal:showCamera',
  620. BTN_REPORT_DOWN: 'btn:reportDown',
  621. } as const,
  622. } as const;
  623. /** 列配置项 */
  624. interface FanColumnItem {
  625. title: string;
  626. value?: string | number;
  627. }
  628. const globalConfig = inject('globalConfig');
  629. const HistoryTable =
  630. globalConfig.History_Type == 'remote'
  631. ? defineAsyncComponent(() => import('../comment/HistoryTable.vue'))
  632. : defineAsyncComponent(() => import('../../../vent/comment/history/HistoryTable.vue'));
  633. const { hasPermission } = usePermission();
  634. const [registerModal, { openModal, closeModal }] = useModal();
  635. const [registerModal1, { openModal: openModal1, closeModal: closeModal1 }] = useModal();
  636. const [registerModalAssistance, { openModal: openAssistance, closeModal: closeAssistance }] = useModal();
  637. const [registerModal2, { openModal: openModal2, closeModal: closeModal2 }] = useModal();
  638. const [registerModal3, { openModal: openModal3, closeModal: closeModal3 }] = useModal();
  639. const { currentRoute } = useRouter();
  640. const router = useRouter();
  641. const { createConfirm } = useMessage();
  642. const globSetting = useGlobSetting();
  643. const showPlay = ref(hasPermission(CONSTS.PERMISSIONS.FAN_LOCAL_SHOW_CAMERA) ? true : false);
  644. const computedStyle = computed(() => {
  645. const hasMatchingPermission = hasPermission(CONSTS.PERMISSIONS.FAN_LOCAL_PJFX);
  646. return hasMatchingPermission ? 'max-height: 200px' : '';
  647. });
  648. const fanTitles = globSetting.sysOrgCode === 'zmhjhzmy' ? ['1#风机', '2#风机'] : [CONSTS.FAN_NAME_MAIN, CONSTS.FAN_NAME_STANDBY];
  649. const modalTypeArr = reactive({
  650. leftBtnArr: [
  651. {
  652. key: 'startSmoke',
  653. value: '启停风机',
  654. permission: 'btn:openclose',
  655. },
  656. {
  657. key: 'startFan',
  658. value: '启停风机',
  659. permission: 'btn:openclose1',
  660. },
  661. {
  662. key: 'changeSmoke', // 主备两个点位
  663. value: '一键倒机',
  664. permission: 'btn:change',
  665. },
  666. {
  667. key: 'changeFan', // 主备一个点位
  668. value: '一键倒机',
  669. permission: 'btn:CtrlFanChange',
  670. },
  671. {
  672. key: 'fan1ToFan2',
  673. value: '主机倒备机',
  674. permission: 'btn:ctrlFan1ToFan2',
  675. },
  676. {
  677. key: 'fan2ToFan1',
  678. value: '备机倒主机',
  679. permission: 'btn:ctrlFan2ToFan1',
  680. },
  681. {
  682. key: 'Fan1Frequency',
  683. value: '主机调频',
  684. permission: 'btn:frequency',
  685. },
  686. {
  687. key: 'Fan2Frequency',
  688. value: '备机调频',
  689. permission: 'btn:frequency',
  690. },
  691. {
  692. key: 'FanFrequency',
  693. value: '风机调频',
  694. permission: 'btn:frequencyMerge',
  695. },
  696. {
  697. key: 'windPower',
  698. value: '风电闭锁',
  699. permission: 'fanLocal:fdbs',
  700. },
  701. {
  702. key: 'gasPower',
  703. value: '瓦斯电闭锁',
  704. permission: 'fanLocal:wsdbs',
  705. },
  706. {
  707. key: 'needAir',
  708. value: '需风量',
  709. permission: 'fanLocal:control',
  710. },
  711. ],
  712. rightBtnArr: [
  713. {
  714. key: 'gasAlarmSet',
  715. value: '瓦斯限值设定',
  716. permission: 'fanLocal:gasAlarmSet',
  717. },
  718. {
  719. key: 'gasOverSet', //
  720. value: '瓦斯限值设定',
  721. permission: 'fanLocal:gasOverSet',
  722. },
  723. {
  724. key: 'kkjc',
  725. value: '工况辅助决策',
  726. permission: 'fanLocal:kkjc',
  727. },
  728. {
  729. key: 'zhlk',
  730. value: '自主联控',
  731. permission: 'fanLocal:zhlk',
  732. },
  733. {
  734. key: 'remote',
  735. value: '就地/远程',
  736. permission: 'fanLocal:remote',
  737. },
  738. {
  739. key: 'diameter',
  740. value: '风筒直径',
  741. permission: 'fanLocal:diameter',
  742. },
  743. {
  744. key: 'diameter',
  745. value: '风筒直径',
  746. permission: 'fanLocal:diameter',
  747. },
  748. {
  749. key: 'len',
  750. value: '风筒长度',
  751. permission: 'fanLocal:len',
  752. },
  753. // {
  754. // key: 'frequency',
  755. // value: '调频',
  756. // permission: 'fanLocal:control',
  757. // },
  758. {
  759. key: 'windPowerLimit',
  760. value: '风电闭锁限值',
  761. permission: 'fanLocal:windPowerLimit',
  762. },
  763. {
  764. key: 'gasPowerLimit',
  765. value: '瓦斯电闭锁限值',
  766. permission: 'fanLocal:gasPowerLimit',
  767. },
  768. {
  769. key: 'airVolumeAlarm',
  770. value: '风量报警',
  771. permission: 'fanLocal:airVolumeAlarm',
  772. min: 0,
  773. max: 100,
  774. },
  775. {
  776. key: 'disAirAlarm',
  777. value: '漏风率报警',
  778. permission: 'fanLocal:disAirAlarm',
  779. },
  780. {
  781. key: 'dischargeGas',
  782. value: '智能排放瓦斯',
  783. permission: 'fanLocal:dischargeGas',
  784. },
  785. {
  786. key: 'supplyAir',
  787. value: '按需供风联动',
  788. permission: 'fanLocal:supplyAir',
  789. },
  790. ],
  791. });
  792. const sensorList = readonly([
  793. {
  794. value: '1',
  795. label: 'T1',
  796. },
  797. {
  798. value: '2',
  799. label: 'T2',
  800. },
  801. {
  802. value: '3',
  803. label: 'T3',
  804. },
  805. ] as const);
  806. const scroll = reactive({
  807. y: CONSTS.SCROLL_Y_DEFAULT,
  808. });
  809. const deviceTypeDicts = getDictItemsByCode(CONSTS.DICT_CODE_FANLOCALTYPE);
  810. const gasWarningVal = ref(CONSTS.GAS_WARNING_VAL_DEFAULT);
  811. const gasWarningVal1 = ref(CONSTS.GAS_WARNING_VAL1_DEFAULT);
  812. const playerRef = ref();
  813. const MonitorDataTable = ref();
  814. const modalSensor = ref(null);
  815. const loading = ref(false);
  816. const renderPlayer = ref(true);
  817. const activeKey = ref('1');
  818. const player1 = ref();
  819. const modalIsShow = ref<boolean>(false); // 是否显示模态框
  820. const modalTitle = ref(''); // 模态框标题显示内容,根据设备操作类型决定
  821. const fan1FrequencyVal = ref(40); //主机频率
  822. const fan2FrequencyVal = ref(40); //备机频率
  823. const mainWindIsShow1 = ref('open'); // 主机默认启动leftColumns
  824. const mainWindIsShow2 = ref('stop'); // 备机默认不启动
  825. const fanControl = ref('');
  826. const targetVolume = ref(600);
  827. const targetVolume1 = ref(600); //目标风量
  828. const historyList = ref([]);
  829. const passWord = ref('');
  830. // 默认初始是第一行
  831. const selectRowIndex = ref(-1);
  832. const dataMonitorRowIndex = ref(0);
  833. // 默认数据右边监测的是主机
  834. const warningMonitorRowIndex = ref(0);
  835. const xAxisDataGas = ref([]);
  836. // 设备数据
  837. const controlType = ref('');
  838. const modalType = ref('');
  839. const mainModelType = ref(CONSTS.DEVICE_TYPE_FANLOCAL);
  840. // 监测数据
  841. const initData = {
  842. deviceID: '',
  843. deviceType: '',
  844. strname: '',
  845. dataDh: '-', //压差
  846. dataDtestq: '-', //测试风量
  847. sourcePressure: '-', //气源压力
  848. dataDequivalarea: '-',
  849. netStatus: '0', //通信状态
  850. warnLevel_str: '',
  851. stationname: '',
  852. fanFrequencyType: '',
  853. };
  854. const frequencyVal = ref(0);
  855. const dataSource = ref([]);
  856. const historySource = ref([]);
  857. // 关联设备信息
  858. const linkDeviceInfo = ref({});
  859. // 监测数据
  860. let selectData = shallowReactive(lodash.cloneDeep(initData));
  861. const currentDeviceData = ref({});
  862. const rightColumns = ref<BasicColumn[]>([]);
  863. const leftColumns = ref<BasicColumn[]>([]);
  864. const leftColumns1 = ref<FanColumnItem[]>([
  865. { title: '供风充足评价', value: '' },
  866. { title: '回风流超限评价', value: '' },
  867. { title: '风筒漏风率评价', value: '' },
  868. ]);
  869. const leftColumns2 = ref<FanColumnItem[]>([
  870. { title: '供风充足评价', value: '充足' },
  871. { title: '回风流超限评价', value: '正常' },
  872. { title: '风筒漏风率评价', value: '正常' },
  873. ]);
  874. const devicekind = ref(deviceTypeDicts && deviceTypeDicts.length > 0 ? deviceTypeDicts[0]['value'] : CONSTS.DEVICE_TYPE_FANLOCAL);
  875. const deviceType = ref(selectData.deviceType);
  876. const headElHeight = ref(0);
  877. let btnClick = ref(true); // 判断按钮是否可点
  878. const modelRef = ref();
  879. /** 模型对应的组件,根据实际情况分为二维三维 */
  880. const modelComponent = shallowRef(getModelComponent(globalConfig.is2DModel));
  881. //报表导出
  882. let editID = ref<any>('');
  883. let fileType = ref('');
  884. const { getCamera, removeCamera } = useCamera();
  885. const echartsDataList = ref<any[]>([]);
  886. const remoteChartsColumns = getTableHeaderColumns('fanlocal_chart');
  887. const chartsColumns = remoteChartsColumns && remoteChartsColumns.length > 0 ? remoteChartsColumns : [];
  888. let chartsColumnsFan1 = [],
  889. chartsColumnsFan2 = [];
  890. // 这里需要处理主备echartColumns
  891. if (chartsColumns.length > 0) {
  892. for (let i = 0; i < chartsColumns.length; i++) {
  893. let itemColumn = chartsColumns[i];
  894. let dataIndexFan1, dataIndexFan2;
  895. if (itemColumn['dataIndex']) {
  896. const dataIndex = itemColumn['dataIndex'] as string;
  897. dataIndexFan1 = dataIndex.startsWith('Fan') ? dataIndex.replace('Fan', 'Fan1') : dataIndex.replace('fan', 'fan1');
  898. dataIndexFan2 = dataIndex.startsWith('Fan') ? dataIndex.replace('Fan', 'Fan2') : dataIndex.replace('fan', 'fan2');
  899. chartsColumnsFan1.push({ ...itemColumn, dataIndex: dataIndexFan1, legend: fanTitles[0] + itemColumn['legend'] });
  900. chartsColumnsFan2.push({ ...itemColumn, dataIndex: dataIndexFan2, legend: fanTitles[1] + itemColumn['legend'] });
  901. }
  902. }
  903. }
  904. watch(deviceType, (type) => {
  905. rightColumns.value = getTableHeaderColumns(type + '_monitor_right') as [];
  906. if (rightColumns.value && rightColumns.value.length < 1) {
  907. rightColumns.value = getTableHeaderColumns(type.split('_')[0] + '_monitor_right') as [];
  908. }
  909. leftColumns.value = getTableHeaderColumns(type + '_monitor_left') as [];
  910. if (leftColumns.value && leftColumns.value.length < 1) {
  911. leftColumns.value = getTableHeaderColumns(type.split('_')[0] + '_monitor_left') as [];
  912. }
  913. });
  914. const flvURL1 = () => {
  915. // return `https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv`;
  916. return '';
  917. };
  918. const changeDeviceKind = (e) => {
  919. devicekind.value = e;
  920. loading.value = true;
  921. selectRowIndex.value = -1;
  922. nextTick(() => {
  923. selectData = lodash.cloneDeep(initData);
  924. loading.value = false;
  925. if (selectData.deviceID) MonitorDataTable.value.setSelectedRowKeys([selectData.deviceID]);
  926. const headEl = document.querySelector(`.zxm-table-thead`);
  927. if (headEl) {
  928. headElHeight.value = headEl.clientHeight;
  929. }
  930. });
  931. };
  932. const changePlay = () => {
  933. showPlay.value = !showPlay.value;
  934. };
  935. const tabChange = (activeKeyVal) => {
  936. activeKey.value = activeKeyVal;
  937. if (activeKeyVal == 1) {
  938. nextTick(() => {
  939. MonitorDataTable.value.setSelectedRowKeys([selectData.deviceID]);
  940. });
  941. }
  942. };
  943. const selectDevice = (key, val) => {
  944. if (key === 'dataMonitorRowIndex') {
  945. dataMonitorRowIndex.value = val;
  946. } else {
  947. warningMonitorRowIndex.value = val;
  948. }
  949. };
  950. // 优化:封装主备机数据取值方法,避免模板重复逻辑
  951. const getFanValue = (dataIndex: string, fanType: 'Fan1' | 'Fan2') => {
  952. if (!selectData) return '-';
  953. const targetKey = dataIndex.startsWith('Fan') ? dataIndex.replace('Fan', fanType) : dataIndex;
  954. return selectData[targetKey] ?? '-';
  955. };
  956. // 优化:重复判断抽离为计算属性,利用缓存减少重复计算
  957. const isShowStandbyFan = computed(() => {
  958. if (!selectData) return false;
  959. const installKind = selectData.install_kind;
  960. return installKind ? !installKind.endsWith('_1') : true;
  961. });
  962. //报表导出点击
  963. async function reportDown() {
  964. openModal1();
  965. let res = await save({ reportType: 'fanlocal' });
  966. console.log(res, 'res-----------');
  967. let list = await reportList({ id: res.id });
  968. console.log(list, 'list-----------');
  969. let index = list.records[0].fileName.indexOf('.');
  970. fileType.value = list.records[0].fileName.substring(index + 1);
  971. editID.value = list.records[0].id;
  972. openModal1();
  973. }
  974. //详情
  975. function goDetail() {
  976. openModal();
  977. }
  978. function goDetailDevice(linkDeviceCode) {
  979. let linkDeviceId = '';
  980. if (linkDeviceCode.startsWith('window')) {
  981. linkDeviceId = linkDeviceInfo.value[linkDeviceCode] ? linkDeviceInfo.value[linkDeviceCode]['id'] : '';
  982. router.push({ path: '/monitorChannel/monitor-window', query: { id: linkDeviceId } });
  983. }
  984. }
  985. //
  986. async function getDataSource() {
  987. if (devicekind.value) {
  988. const res = await list({ devicetype: devicekind.value, pagetype: 'normal' });
  989. // const res = await list({ devicetype: 'fanlocal', pagetype: 'normal' });
  990. if (res.msgTxt && res.msgTxt[0] && res.msgTxt[0].datalist && res.msgTxt[0].datalist.length > 0) {
  991. const dataArr = res.msgTxt[0].datalist || [];
  992. dataSource.value = [];
  993. dataArr.forEach((data) => {
  994. const readData = data.readData;
  995. data = Object.assign(data, readData);
  996. if (data['Fan1StartStatus'] && data['Fan1StartStatus'] === '1.0') data['Fan1StartStatus'] = '1';
  997. if (data['Fan2StartStatus'] && data['Fan2StartStatus'] === '1.0') data['Fan2StartStatus'] = '1';
  998. if (data['Fan1StartStatus'] && data['Fan1StartStatus'] === '0.0') data['Fan1StartStatus'] = '0';
  999. if (data['Fan2StartStatus'] && data['Fan2StartStatus'] === '0.0') data['Fan2StartStatus'] = '0';
  1000. data['windQuantity2'] =
  1001. data['windQuantity2'] ||
  1002. data['m3'] ||
  1003. data['ductOutletAirVolume_merge'] ||
  1004. data['windOutSpeed_merge'] ||
  1005. data['windOutSpeed1'] ||
  1006. data['windOutSpeed2'] ||
  1007. data['windOutSpeed_merge'];
  1008. // if (globSetting.sysOrgCode === 'sdmtjtbetmk') {
  1009. // if (data['m3']) data['ductOutletAirVolume_merge'] = data['m3'];
  1010. // if (data['m3']) data['inletAirVolume_merge'] = (Number(data['m3']) * 1.08).toFixed(2);
  1011. // }
  1012. dataSource.value.push(data);
  1013. const m3 = data.windSpeed_merge;
  1014. const fsectarea = data.windSectionArea_merge;
  1015. const needq = data.needq;
  1016. const airVolume = m3 * fsectarea; // 计算风量
  1017. // 更新供风充足评价
  1018. leftColumns1.value[0].value = airVolume >= needq ? '正常' : '不充足';
  1019. if (data.tunnelType === 'coalTunnel') {
  1020. leftColumns1.value[1].value = m3 < 0.25 ? '超限' : '正常';
  1021. } else if (data.tunnelType === 'rockTunnel' || data.tunnelType === 'coalRockTunnel') {
  1022. leftColumns1.value[1].value = m3 < 0.15 ? '超限' : '正常';
  1023. }
  1024. // if (data.airLeakageRatePerHundredX !== '' && data.airLeakageRatePerHundred_merge !== '') {
  1025. // leftColumns1.value[2].value = data.airLeakageRatePerHundred_merge <= data.airLeakageRatePerHundredX ? '正常' : '不满足';
  1026. // } else {
  1027. // leftColumns1.value[2].value = '-';
  1028. // }
  1029. // console.log(data.airLeakageRatePerHundredX, data.airLeakageRatePerHundred_merge, '漏风率数据');
  1030. leftColumns1.value[2].value =
  1031. data.airLeakageRatePerHundredX > 0 ? (data.airLeakageRatePerHundred_merge <= data.airLeakageRatePerHundredX ? '正常' : '不满足') : '-';
  1032. });
  1033. if (selectRowIndex.value > -1) {
  1034. const data = dataArr[selectRowIndex.value];
  1035. currentDeviceData.value = data;
  1036. // 存放echarts数据
  1037. if (data && data['readTime']) {
  1038. const dataList = cloneDeep(echartsDataList.value);
  1039. if (dataList.length == 0 || dataList[dataList.length - 1]['readTime'] !== data['readTime']) {
  1040. if (dataList.length < 15) {
  1041. dataList.push({ ...data });
  1042. } else {
  1043. dataList.shift();
  1044. dataList.push({ ...data });
  1045. }
  1046. echartsDataList.value = dataList;
  1047. }
  1048. }
  1049. // 制动控制echarts数据获取
  1050. let echartsData = dataArr[selectRowIndex.value]['history'] || [];
  1051. echartsData = echartsData.filter((item) => {
  1052. item['FanfHz'] = data['Fan1StartStatus'] == '1' ? item['Fan1fHz'] : data['Fan2StartStatus'] == '1' ? item['Fan2fHz'] : 0;
  1053. item['windQuantity2'] =
  1054. item['windQuantity2'] ||
  1055. item['m3'] ||
  1056. item['ductOutletAirVolume_merge'] ||
  1057. item['windOutSpeed_merge'] ||
  1058. item['windOutSpeed1'] ||
  1059. item['windOutSpeed2'] ||
  1060. item['windOutSpeed_merge'];
  1061. return true;
  1062. });
  1063. historyList.value = echartsData;
  1064. }
  1065. } else {
  1066. return (dataSource.value = []);
  1067. }
  1068. } else {
  1069. dataSource.value = [];
  1070. }
  1071. }
  1072. // https获取监测数据
  1073. let timer: null | NodeJS.Timeout = null;
  1074. async function getMonitor(flag?) {
  1075. if (Object.prototype.toString.call(timer) === '[object Null]') {
  1076. timer = await setTimeout(
  1077. async () => {
  1078. await getDataSource();
  1079. if (dataSource.value.length > 0 && selectRowIndex.value == -1 && MonitorDataTable.value) {
  1080. // 初始打开页面
  1081. if (flag && currentRoute.value && currentRoute.value['query'] && currentRoute.value['query']['id']) {
  1082. MonitorDataTable.value.setSelectedRowKeys(currentRoute.value['query']['id']);
  1083. } else {
  1084. MonitorDataTable.value.setSelectedRowKeys(dataSource.value[0]['deviceID']);
  1085. }
  1086. }
  1087. for (const key in selectData) {
  1088. selectData[key] = '';
  1089. }
  1090. if (dataSource.value.length > 0 && dataSource.value[selectRowIndex.value]) {
  1091. deviceType.value = dataSource.value[selectRowIndex.value]['deviceType'];
  1092. if (dataSource.value.length > 0 && dataSource.value[selectRowIndex.value]) {
  1093. Object.assign(selectData, dataSource.value[selectRowIndex.value]);
  1094. }
  1095. selectData['modalTyoe'] = selectData['install_kind'];
  1096. playSmoke(selectData);
  1097. modelRef.value?.animate?.(selectData);
  1098. addText(selectData, fanDualArray.value);
  1099. }
  1100. historySource.value = selectData.history;
  1101. if (timer) {
  1102. timer = null;
  1103. }
  1104. getMonitor();
  1105. },
  1106. flag ? 0 : 1000
  1107. );
  1108. }
  1109. }
  1110. // 获取设备基本信息列表
  1111. const deviceBaseList = ref([]);
  1112. function getDeviceBaseList() {
  1113. getTableList({ pageSize: 1000 }).then((res) => {
  1114. deviceBaseList.value = res.records;
  1115. });
  1116. }
  1117. // 切换检测数据
  1118. async function getSelectRow(id) {
  1119. if (!id || id == selectData['deviceID']) return;
  1120. // loading.value = true;
  1121. const selectIndex: any = dataSource.value.findIndex((baseData: any) => baseData.deviceID == id);
  1122. selectRowIndex.value = selectIndex;
  1123. nextTick(() => {
  1124. const headEl = document.querySelector(`.zxm-table-thead`);
  1125. if (headEl) {
  1126. headElHeight.value = headEl.clientHeight;
  1127. }
  1128. const data = dataSource.value[selectIndex];
  1129. if (data) {
  1130. if (selectData['linkInfo']) linkDeviceInfo.value = JSON.parse(selectData['linkInfo']);
  1131. if (linkDeviceInfo.value['window_fWindowM3']) {
  1132. modalType.value = 'fc';
  1133. }
  1134. // 主备互斥控制
  1135. if (data['Fan1StartStatus'] == '1') {
  1136. mainWindIsShow1.value = 'open';
  1137. mainWindIsShow2.value = 'stop';
  1138. selectDevice('warningMonitorRowIndex', 0);
  1139. selectDevice('dataMonitorRowIndex', 0);
  1140. } else if (data['Fan2StartStatus'] == '1') {
  1141. mainWindIsShow2.value = 'open';
  1142. mainWindIsShow1.value = 'stop';
  1143. selectDevice('warningMonitorRowIndex', 1);
  1144. selectDevice('dataMonitorRowIndex', 1);
  1145. }
  1146. const xAxisDataGasArr = [];
  1147. for (const key in selectData) {
  1148. if (key.startsWith('gas') && key.length < 5) {
  1149. xAxisDataGasArr.push({ key: 'T' + key.substring(3), valueKey: key });
  1150. }
  1151. }
  1152. xAxisDataGas.value = xAxisDataGasArr;
  1153. // 根据安装类别分辨是单巷还是双巷模型,以及那个风机应该高亮
  1154. if (data['install_kind']) {
  1155. const keymap = {
  1156. fanLocalTwo: ['fanLocalTwo', modalType.value],
  1157. single: ['fanLocal', modalType.value], //单巷(默认两风筒)
  1158. dual_inner: ['fanLocalDual', 'inner'],
  1159. dual_outer: ['fanLocalDual', 'outer'],
  1160. fanlocal_1: ['fanLocalSingle', modalType.value], //单巷单风筒
  1161. };
  1162. mainModelType.value = keymap[data['install_kind']][0];
  1163. modalType.value = keymap[data['install_kind']][1];
  1164. } else {
  1165. // 为了兼容没有添加 install_kind 的旧的单巷
  1166. mainModelType.value = 'fanLocal';
  1167. }
  1168. }
  1169. setModelType(mainModelType.value, modalType.value, fanDualArray.value);
  1170. addCssText();
  1171. });
  1172. await getCamera(id, playerRef, renderPlayer);
  1173. return;
  1174. }
  1175. // 打开并设置modal的标题
  1176. function showModal(obj) {
  1177. if (!btnClick.value) return;
  1178. if (obj.key == 'kkjc') {
  1179. gasWarningVal.value = CONSTS.GAS_WARNING_VAL_DEFAULT;
  1180. // 工况辅助决策
  1181. openAssistance(true, {});
  1182. return;
  1183. }
  1184. controlType.value = obj.key;
  1185. modalTitle.value = obj.value;
  1186. passWord.value = '';
  1187. modalIsShow.value = true;
  1188. }
  1189. function changeMotor(e) {
  1190. const target = e.target;
  1191. if (target.name === 'localWind1') {
  1192. if (target.value === 'open') {
  1193. mainWindIsShow2.value = 'stop';
  1194. }
  1195. } else if (target.name === 'localWind2') {
  1196. if (target.value === 'open') {
  1197. mainWindIsShow1.value = 'stop';
  1198. }
  1199. }
  1200. }
  1201. function handleOk(control?) {
  1202. if (passWord.value == '') {
  1203. message.warning('请输入密码!');
  1204. return;
  1205. }
  1206. createConfirm({
  1207. iconType: 'warning',
  1208. title: '控制',
  1209. content: '您确定要控制吗?',
  1210. onOk: () => handerFn(),
  1211. });
  1212. const handerFn = () => {
  1213. const handType = controlType.value;
  1214. const data = {
  1215. deviceid: selectData.deviceID,
  1216. devicetype: selectData.deviceType,
  1217. paramcode: '',
  1218. password: passWord.value || globalConfig?.simulatedPassword,
  1219. value: null,
  1220. };
  1221. if (handType === 'startSmoke') {
  1222. // 启动风机
  1223. // 以下是互斥
  1224. if (mainWindIsShow1.value === 'open' && mainWindIsShow2.value === 'stop') {
  1225. // playSmoke(handType, 'top', frequency, 'open');
  1226. data.paramcode = 'CtrlFan1Start';
  1227. deviceControlApi(data)
  1228. .then((res) => {
  1229. if (res.success) {
  1230. if (globalConfig.History_Type == 'remote') {
  1231. message.success('指令已下发至生产管控平台成功!');
  1232. } else {
  1233. message.success('指令已下发成功!');
  1234. }
  1235. modalTitle.value = '';
  1236. modalIsShow.value = false;
  1237. } else {
  1238. message.error(res.message);
  1239. }
  1240. })
  1241. .catch((err) => {
  1242. // modalIsShow.value = true;
  1243. });
  1244. } else if (mainWindIsShow2.value === 'open' && mainWindIsShow1.value === 'stop') {
  1245. // playSmoke(handType, 'down', frequency, 'open');
  1246. data.paramcode = 'CtrlFan2Start';
  1247. deviceControlApi(data)
  1248. .then(() => {
  1249. if (res.success) {
  1250. if (globalConfig.History_Type == 'remote') {
  1251. message.success('指令已下发至生产管控平台成功!');
  1252. } else {
  1253. message.success('指令已下发成功!');
  1254. }
  1255. modalTitle.value = '';
  1256. modalIsShow.value = false;
  1257. } else {
  1258. message.error(res.message);
  1259. }
  1260. })
  1261. .catch((err) => {});
  1262. } else if (mainWindIsShow1.value === 'stop' && mainWindIsShow2.value === 'stop') {
  1263. // playSmoke(handType, '', frequency, 'stop');
  1264. }
  1265. } else if (handType === 'startFan') {
  1266. if (control === 'Fan1Open') {
  1267. data.paramcode = 'CtrlFan1Start';
  1268. } else if (control === 'Fan2Open') {
  1269. data.paramcode = 'CtrlFan2Start';
  1270. } else if (control === 'Fan1Stop') {
  1271. data.paramcode = 'CtrlFan1Stop';
  1272. } else if (control === 'Fan2Stop') {
  1273. data.paramcode = 'CtrlFan2Stop';
  1274. } else if (control === 'Fan1Reset') {
  1275. data.paramcode = 'CtrlFan1Reset';
  1276. } else if (control === 'Fan2Reset') {
  1277. data.paramcode = 'CtrlFan2Reset';
  1278. }
  1279. deviceControlApi(data)
  1280. .then((res) => {
  1281. if (res.success) {
  1282. if (globalConfig.History_Type == 'remote') {
  1283. message.success('指令已下发至生产管控平台成功!');
  1284. } else {
  1285. message.success('指令已下发成功!');
  1286. }
  1287. modalTitle.value = '';
  1288. modalIsShow.value = false;
  1289. } else {
  1290. message.error(res.message);
  1291. }
  1292. })
  1293. .catch((err) => {
  1294. btnClick.value = true;
  1295. });
  1296. } else if (handType === 'Fan1Frequency' || handType === 'Fan2Frequency' || handType === 'FanFrequency') {
  1297. // 调频
  1298. if (handType === 'Fan1Frequency') {
  1299. data.paramcode = 'Fan1FreqHz';
  1300. data.value = fan1FrequencyVal.value;
  1301. } else if (handType === 'Fan2Frequency') {
  1302. data.paramcode = 'Fan2FreqHz';
  1303. data.value = fan2FrequencyVal.value;
  1304. } else if (handType === 'FanFrequency') {
  1305. data.paramcode = 'FreqHz_merge';
  1306. data.value = fan1FrequencyVal.value;
  1307. }
  1308. deviceControlApi(data)
  1309. .then((res) => {
  1310. if (res.success) {
  1311. if (globalConfig.History_Type == 'remote') {
  1312. message.success('指令已下发至生产管控平台成功!');
  1313. } else {
  1314. message.success('指令已下发成功!');
  1315. }
  1316. modalTitle.value = '';
  1317. modalIsShow.value = false;
  1318. } else {
  1319. message.error(res.message);
  1320. }
  1321. })
  1322. .catch((err) => {});
  1323. } else if (handType === 'changeSmoke') {
  1324. if (selectData['Fan1StartStatus'] == '0' || !selectData['Fan1StartStatus']) {
  1325. data.paramcode = 'CtrlFan1Start';
  1326. deviceControlApi(data)
  1327. .then((res) => {
  1328. if (res.success) {
  1329. if (globalConfig.History_Type == 'remote') {
  1330. message.success('指令已下发至生产管控平台成功!');
  1331. } else {
  1332. message.success('指令已下发成功!');
  1333. }
  1334. modalTitle.value = '';
  1335. modalIsShow.value = false;
  1336. } else {
  1337. message.error(res.message);
  1338. }
  1339. mainWindIsShow1.value = 'stop';
  1340. mainWindIsShow2.value = 'open';
  1341. })
  1342. .catch((err) => {});
  1343. } else if (selectData['Fan2StartStatus'] == '0' || !selectData['Fan2StartStatus']) {
  1344. data.paramcode = 'CtrlFan2Start';
  1345. deviceControlApi(data)
  1346. .then((res) => {
  1347. if (res.success) {
  1348. if (globalConfig.History_Type == 'remote') {
  1349. message.success('指令已下发至生产管控平台成功!');
  1350. } else {
  1351. message.success('指令已下发成功!');
  1352. }
  1353. modalTitle.value = '';
  1354. modalIsShow.value = false;
  1355. } else {
  1356. message.error(res.message);
  1357. }
  1358. mainWindIsShow1.value = 'open';
  1359. mainWindIsShow2.value = 'stop';
  1360. })
  1361. .catch((err) => {});
  1362. }
  1363. // // 一键倒机
  1364. // if (mainWindIsShow1.value === 'open' && mainWindIsShow2.value === 'stop') {
  1365. // // playSmoke('startSmoke', 'down', frequency, 'open');
  1366. // data.paramcode = 'CtrlFan2Start';
  1367. // deviceControlApi(data).then((res) => {
  1368. // console.log('设备操作结果', res);
  1369. // modalTitle.value = '';
  1370. // modalIsShow.value = false;
  1371. // }).catch((err) => {
  1372. // });
  1373. // mainWindIsShow1.value = 'stop';
  1374. // mainWindIsShow2.value = 'open';
  1375. // } else if (mainWindIsShow2.value === 'open' && mainWindIsShow1.value === 'stop') {
  1376. // // playSmoke('startSmoke', 'top', frequency, 'open');
  1377. // data.paramcode = 'CtrlFan1Start';
  1378. // deviceControlApi(data).then((res) => {
  1379. // console.log('设备操作结果', res);
  1380. // modalTitle.value = '';
  1381. // modalIsShow.value = false;
  1382. // }).catch((err) => {
  1383. // });
  1384. // mainWindIsShow1.value = 'open';
  1385. // mainWindIsShow2.value = 'stop';
  1386. // } else if (mainWindIsShow1.value === 'stop' && mainWindIsShow2.value === 'stop') {
  1387. // // playSmoke(handType, '', frequency, 'stop');
  1388. // }
  1389. } else if (handType === 'changeFan') {
  1390. data.paramcode = 'CtrlFanStart';
  1391. deviceControlApi(data)
  1392. .then((res) => {
  1393. if (res.success) {
  1394. if (globalConfig.History_Type == 'remote') {
  1395. message.success('指令已下发至生产管控平台成功!');
  1396. } else {
  1397. message.success('指令已下发成功!');
  1398. }
  1399. } else {
  1400. message.error(res.message);
  1401. }
  1402. modalTitle.value = '';
  1403. modalIsShow.value = false;
  1404. })
  1405. .catch((err) => {});
  1406. } else if (handType === 'fan1ToFan2') {
  1407. data.paramcode = 'CtrlFan1ToFan2';
  1408. deviceControlApi(data).then((res) => {
  1409. if (res.success) {
  1410. if (globalConfig.History_Type == 'remote') {
  1411. message.success('指令已下发至生产管控平台成功!');
  1412. } else {
  1413. message.success('指令已下发成功!');
  1414. }
  1415. modalTitle.value = '';
  1416. modalIsShow.value = false;
  1417. } else {
  1418. message.error(res.message);
  1419. }
  1420. });
  1421. } else if (handType === 'fan2ToFan1') {
  1422. data.paramcode = 'CtrlFan2ToFan1';
  1423. deviceControlApi(data).then((res) => {
  1424. if (res.success) {
  1425. if (globalConfig.History_Type == 'remote') {
  1426. message.success('指令已下发至生产管控平台成功!');
  1427. } else {
  1428. message.success('指令已下发成功!');
  1429. }
  1430. modalTitle.value = '';
  1431. modalIsShow.value = false;
  1432. } else {
  1433. message.error(res.message);
  1434. }
  1435. });
  1436. } else if (handType === 'gasAlarmSet') {
  1437. if (passWord.value != '123456') {
  1438. message.error('密码错误,请重新输入!');
  1439. return;
  1440. }
  1441. if (gasWarningVal.value) {
  1442. modalTitle.value = '';
  1443. modalIsShow.value = false;
  1444. setTimeout(() => {
  1445. passWord.value = '';
  1446. modalIsShow.value = true;
  1447. controlType.value = 'toGkjc';
  1448. modalTitle.value = '工况决策辅助';
  1449. }, 500);
  1450. } else {
  1451. message.error('请核对瓦斯超限浓度值!');
  1452. }
  1453. } else if (handType === 'toGkjc') {
  1454. if (passWord.value != '123456') {
  1455. message.error('密码错误,请重新输入!');
  1456. return;
  1457. }
  1458. //进行工况决策
  1459. if (selectData.Fan1StartStatus == '1') {
  1460. openAssistance(true, {
  1461. // m3: selectData.m3 || 675.87,
  1462. m3: 675.87,
  1463. frequency: 30,
  1464. // m3: 525.87, //5.0测试数据
  1465. // frequency: 35,
  1466. // frequency: selectData.Fan1fHz || selectData.Fan1FreqHz || selectData.Fan1Loop_Frequency,
  1467. gasWarningVal: gasWarningVal.value,
  1468. isCompute: false,
  1469. });
  1470. } else if (selectData.Fan2StartStatus == '1') {
  1471. openAssistance(true, {
  1472. // m3: selectData.m3 || 675.87,
  1473. m3: 675.87,
  1474. frequency: 30,
  1475. // frequency: selectData.Fan2fHz || selectData.Fan2FreqHz || selectData.Fan2Loop_Frequency,
  1476. gasWarningVal: gasWarningVal.value,
  1477. isCompute: false,
  1478. });
  1479. } else {
  1480. // openAssistance(true, { m3: 635.04, dataDh: 5748 });
  1481. openAssistance(true);
  1482. }
  1483. modalTitle.value = '';
  1484. modalIsShow.value = false;
  1485. } else if (handType === 'zhlk' || handType === 'gasOverSet') {
  1486. modalIsShow.value = false;
  1487. if (targetVolume.value) {
  1488. const params =
  1489. handType === 'zhlk'
  1490. ? { auto: 1, fanlocalId: selectData.deviceID, xufengliang: targetVolume.value }
  1491. : { auto: 1, fanlocalId: selectData.deviceID, gasMax: gasWarningVal.value };
  1492. autoAdjust(params)
  1493. .then(() => {
  1494. if (hasPermission('echart:show')) activeKey.value = '2';
  1495. if (globalConfig.History_Type == 'remote') {
  1496. message.success('指令已下发至生产管控平台成功!');
  1497. } else {
  1498. message.success('指令已下发成功!');
  1499. }
  1500. modalTitle.value = '';
  1501. })
  1502. .catch(() => {
  1503. message.error('指令下发失败');
  1504. });
  1505. }
  1506. } else if (handType === 'supplyAir') {
  1507. modalIsShow.value = false;
  1508. if (targetVolume1.value) {
  1509. const params = { auto: 1, fanlocalId: selectData.deviceID, xufengliang: targetVolume1.value };
  1510. autoAdjust(params)
  1511. .then(() => {
  1512. if (hasPermission('echart:show')) activeKey.value = '2';
  1513. if (globalConfig.History_Type == 'remote') {
  1514. message.success('指令已下发至生产管控平台成功!');
  1515. } else {
  1516. message.success('指令已下发成功!');
  1517. }
  1518. modalTitle.value = '';
  1519. })
  1520. .catch(() => {
  1521. message.error('指令下发失败');
  1522. });
  1523. }
  1524. openModal2(true, {});
  1525. } else if (handType === 'dischargeGas') {
  1526. modalIsShow.value = false;
  1527. if (gasWarningVal1.value) {
  1528. const params = { auto: 1, fanlocalId: selectData.deviceID, gasMax: gasWarningVal1.value };
  1529. autoAdjust(params)
  1530. .then(() => {
  1531. if (hasPermission('echart:show')) activeKey.value = '2';
  1532. if (globalConfig.History_Type == 'remote') {
  1533. message.success('指令已下发至生产管控平台成功!');
  1534. } else {
  1535. message.success('指令已下发成功!');
  1536. }
  1537. modalTitle.value = '';
  1538. })
  1539. .catch(() => {
  1540. message.error('指令下发失败');
  1541. });
  1542. }
  1543. openModal3(true, {});
  1544. } else if (handType === 'remote') {
  1545. if (control === 'Fan1remote') {
  1546. data.paramcode = 'Fan1autoRoManualControl';
  1547. data.value = true;
  1548. } else if (control === 'Fan2remote') {
  1549. data.paramcode = 'Fan2autoRoManualControl';
  1550. data.value = true;
  1551. }
  1552. deviceControlApi(data)
  1553. .then((res) => {
  1554. if (res.success) {
  1555. if (globalConfig.History_Type == 'remote') {
  1556. message.success('指令已下发至生产管控平台成功!');
  1557. } else {
  1558. message.success('指令已下发成功!');
  1559. }
  1560. modalTitle.value = '';
  1561. modalIsShow.value = false;
  1562. } else {
  1563. message.error(res.message);
  1564. }
  1565. })
  1566. .catch((err) => {
  1567. btnClick.value = true;
  1568. });
  1569. }
  1570. };
  1571. }
  1572. function cancel() {
  1573. modalTitle.value = '';
  1574. modalIsShow.value = false;
  1575. gasWarningVal.value = 0;
  1576. }
  1577. function handleChangeSensor(value: string) {
  1578. console.log(value);
  1579. }
  1580. function addPlayVideo() {
  1581. if (player1.value && player1.value.play) {
  1582. if (!player1.value.paused()) player1.value.play();
  1583. document.body.removeEventListener('mousedown', addPlayVideo);
  1584. }
  1585. }
  1586. function deviceEdit(e: Event, type: string, record) {
  1587. e.stopPropagation();
  1588. openModal(true, {
  1589. type,
  1590. deviceId: record['deviceID'],
  1591. });
  1592. }
  1593. /** 双巷风机数组,第一项是外侧风机,第二项是内侧风机 */
  1594. const fanDualArray = computed<Record<string, any>[]>(() => {
  1595. const inx = selectRowIndex.value;
  1596. const data: any = dataSource.value[inx];
  1597. // 一个外侧风机理论上对应一个内侧的风机
  1598. if (data && data.install_kind && data.install_kind === 'dual_outer') {
  1599. const dualInner = dataSource.value.find((e: any) => e.deviceId == data.associated_id) || {};
  1600. return [data, dualInner];
  1601. }
  1602. if (data && data.install_kind && data.install_kind === 'dual_inner') {
  1603. const dualOuter = dataSource.value.find((e: any) => e.deviceId == data.associated_id) || {};
  1604. return [dualOuter, data];
  1605. }
  1606. return [{}, {}];
  1607. });
  1608. onBeforeMount(() => {
  1609. getDeviceBaseList();
  1610. });
  1611. onMounted(() => {
  1612. const { query } = unref(currentRoute);
  1613. if (query['deviceType']) devicekind.value = query['deviceType'] as string;
  1614. if (globalConfig.is2DModel) {
  1615. getMonitor(true);
  1616. } else {
  1617. mountedThree('#fanLocal3D', ['#fanLocal3DCSS']).then(async () => {
  1618. await getMonitor(true);
  1619. nextTick(async () => {
  1620. // addCssText();
  1621. // setModelType(mainModelType.value, modalType.value);
  1622. });
  1623. });
  1624. }
  1625. document.body.addEventListener('mousedown', addPlayVideo, false);
  1626. });
  1627. onBeforeUnmount(() => {
  1628. removeCamera(playerRef);
  1629. });
  1630. onUnmounted(() => {
  1631. destroy();
  1632. if (timer) {
  1633. clearTimeout(timer);
  1634. timer = undefined;
  1635. }
  1636. });
  1637. </script>
  1638. <style scoped lang="less">
  1639. @import '/@/design/theme.less';
  1640. @import '/@/design/vent/modal.less';
  1641. :deep(.@{ventSpace}-tabs-tabpane-active) {
  1642. height: 100%;
  1643. }
  1644. :deep(.zxm-tabs-content) {
  1645. height: 100%;
  1646. }
  1647. @{theme-deepblue} {
  1648. .scene-box {
  1649. --image-lr-top-bg: url('/@/assets/images/themify/deepblue/vent/lr-top-bg.png');
  1650. --image-lr-tab-bg: url('/@/assets/images/themify/deepblue/vent/lr-tab-bg.png');
  1651. --image-l-tab-active: url('/@/assets/images/themify/deepblue/vent/l-tab-active.png');
  1652. --image-r-tab-active: url('/@/assets/images/themify/deepblue/vent/r-tab-active.png');
  1653. --image-plane-bottom: url('/@/assets/images/themify/deepblue/vent/plane-bottom.png');
  1654. --image-plane-icon-bg: url('/@/assets/images/themify/deepblue/vent/plane-icon-bg.png');
  1655. --container-group-bg: linear-gradient(to right, #ffffff22, #53576305);
  1656. }
  1657. }
  1658. .scene-box {
  1659. --image-lr-top-bg: url('/@/assets/images/vent/lr-top-bg.png');
  1660. --image-lr-tab-bg: url('/@/assets/images/vent/lr-tab-bg.png');
  1661. --image-l-tab-active: url('/@/assets/images/vent/l-tab-active.png');
  1662. --image-r-tab-active: url('/@/assets/images/vent/r-tab-active.png');
  1663. --image-plane-bottom: url('/@/assets/images/vent/plane-bottom.png');
  1664. --image-plane-icon-bg: url('/@/assets/images/vent/plane-icon-bg.png');
  1665. --container-group-bg: linear-gradient(to right, #00deff22, #2081ff05);
  1666. .title-text {
  1667. height: 32px;
  1668. }
  1669. .bottom-tabs-box {
  1670. height: 280px;
  1671. .tabs-box {
  1672. position: relative !important;
  1673. }
  1674. }
  1675. }
  1676. .data-show-box {
  1677. position: relative;
  1678. display: flex;
  1679. flex-direction: row;
  1680. justify-content: space-between;
  1681. padding: 10px 5px;
  1682. color: var(--vent-font-color);
  1683. z-index: 999;
  1684. top: 60px;
  1685. .data-item {
  1686. pointer-events: auto;
  1687. .item-header {
  1688. width: 374px;
  1689. background: var(--image-lr-top-bg) no-repeat;
  1690. background-size: auto;
  1691. height: 32px;
  1692. text-align: center;
  1693. line-height: 34px;
  1694. font-size: 15px;
  1695. font-weight: 600;
  1696. color: #fafafa;
  1697. }
  1698. .item-container {
  1699. width: 346px;
  1700. margin: 0 14px;
  1701. padding: 10px;
  1702. background: #00377c33;
  1703. backdrop-filter: blur(2px);
  1704. .tab {
  1705. width: 323px;
  1706. background: var(--image-lr-tab-bg) no-repeat;
  1707. display: flex;
  1708. .tab-item {
  1709. flex: 1;
  1710. text-align: center;
  1711. padding-top: 2px;
  1712. color: #ffffff99;
  1713. cursor: pointer;
  1714. }
  1715. .tab-item-active-l {
  1716. color: var(--vent-font-action-link);
  1717. background-image: var(--image-l-tab-active);
  1718. background-repeat: no-repeat;
  1719. background-size: auto;
  1720. background-position: 6px 3px;
  1721. }
  1722. .tab-item-active-r {
  1723. color: var(--vent-font-action-link);
  1724. background-image: var(--image-r-tab-active);
  1725. background-repeat: no-repeat;
  1726. background-position: 0 3px;
  1727. }
  1728. }
  1729. .container-group {
  1730. width: 314px;
  1731. margin: 0px 4px;
  1732. background: var(--container-group-bg);
  1733. max-height: 440px;
  1734. overflow-y: auto;
  1735. }
  1736. .container-item {
  1737. width: 100%;
  1738. height: 60px;
  1739. display: flex;
  1740. padding: 10px 0 0 20px;
  1741. margin-bottom: 5px;
  1742. position: relative;
  1743. background: var(--image-plane-bottom) no-repeat;
  1744. background-size: auto;
  1745. background-position: bottom;
  1746. &::before {
  1747. content: '';
  1748. display: block;
  1749. width: 100%;
  1750. height: 5px;
  1751. position: absolute;
  1752. top: 62px;
  1753. left: 0;
  1754. background-color: #73f4ff66;
  1755. backdrop-filter: blur(5px);
  1756. }
  1757. .item-icon {
  1758. width: 54px;
  1759. height: 45px;
  1760. background: var(--image-plane-icon-bg) no-repeat;
  1761. background-size: cover;
  1762. .icon-style {
  1763. font-size: 18px;
  1764. margin: 10px 0 0 20px;
  1765. color: #ffc800;
  1766. }
  1767. }
  1768. .item-name {
  1769. width: 180px;
  1770. display: flex;
  1771. align-items: center; /* 垂直居中 */
  1772. word-wrap: break-word;
  1773. white-space: normal;
  1774. }
  1775. .item-value {
  1776. position: relative;
  1777. height: 26px;
  1778. line-height: 24px;
  1779. margin: 15px 0;
  1780. text-align: center;
  1781. width: 80px;
  1782. border: 1px solid var(--vent-base-border);
  1783. border-radius: 13px;
  1784. background: linear-gradient(to right, #00f5fe44, #0090ff44);
  1785. &::before {
  1786. width: 6px;
  1787. height: 6px;
  1788. content: '';
  1789. position: absolute;
  1790. left: -3px;
  1791. top: 8px;
  1792. background: #ffa500;
  1793. border-radius: 3px;
  1794. }
  1795. }
  1796. .text-red-bold {
  1797. color: red;
  1798. font-weight: bold;
  1799. }
  1800. }
  1801. .warning-header {
  1802. display: flex;
  1803. font-size: 14px;
  1804. .header-item {
  1805. flex: 1;
  1806. display: flex;
  1807. justify-content: center;
  1808. align-items: center;
  1809. .header-title {
  1810. width: 100%;
  1811. text-align: center;
  1812. padding: 1px 0;
  1813. color: var(--vent-font-action-link);
  1814. margin-top: 10px;
  1815. background: #1eb0ff22;
  1816. }
  1817. .header-value {
  1818. // width: 133px;
  1819. height: 36px;
  1820. font-weight: 600;
  1821. font-family: 'douyuFont';
  1822. font-size: 16px;
  1823. color: #ffa500;
  1824. display: flex;
  1825. justify-content: center;
  1826. align-items: center;
  1827. margin-top: 15px;
  1828. margin-left: 8px;
  1829. }
  1830. }
  1831. }
  1832. .warning-group {
  1833. padding: 0 10px;
  1834. position: relative;
  1835. .warning-item {
  1836. display: flex;
  1837. flex-direction: row;
  1838. justify-content: space-between;
  1839. align-items: center;
  1840. height: 38px;
  1841. .item-name {
  1842. display: flex;
  1843. align-items: center;
  1844. // padding-left: 5px;
  1845. .icon {
  1846. width: 6px;
  1847. height: 6px;
  1848. display: inline-block;
  1849. background-color: var(--vent-base-light-bg);
  1850. border-radius: 3px;
  1851. position: relative;
  1852. margin-right: 8px;
  1853. &::before {
  1854. content: '';
  1855. width: 10px;
  1856. height: 10px;
  1857. display: block;
  1858. border: 1px solid #34edff99;
  1859. border-radius: 5px;
  1860. position: absolute;
  1861. top: -2px;
  1862. left: -2px;
  1863. }
  1864. }
  1865. &::before {
  1866. content: '';
  1867. display: block;
  1868. width: 1px;
  1869. height: 38px;
  1870. position: absolute;
  1871. left: 12px;
  1872. background-color: var(--vent-base-light-bg);
  1873. }
  1874. }
  1875. }
  1876. }
  1877. .warning-group-r {
  1878. &::before {
  1879. content: '';
  1880. display: block;
  1881. width: 1px;
  1882. height: 100%;
  1883. position: absolute;
  1884. left: 12px;
  1885. background-color: var(--vent-base-light-bg);
  1886. }
  1887. }
  1888. }
  1889. }
  1890. }
  1891. .input-box {
  1892. display: flex;
  1893. align-items: center;
  1894. .input-title {
  1895. color: #73e8fe;
  1896. width: auto;
  1897. }
  1898. margin-right: 10px;
  1899. }
  1900. .label {
  1901. max-width: 220px;
  1902. }
  1903. #fanLocalSelectDom {
  1904. :deep(.@{ventSpace}-select-dropdown) {
  1905. left: 0px !important;
  1906. top: 35px !important;
  1907. }
  1908. }
  1909. .@{ventSpace}-input {
  1910. width: 150px;
  1911. }
  1912. :deep(#LivePlayerBox) {
  1913. display: flex;
  1914. flex-direction: row;
  1915. justify-content: flex-end;
  1916. padding-right: 380px;
  1917. pointer-events: none;
  1918. .liveVideo {
  1919. align-self: auto !important;
  1920. }
  1921. .video-parent {
  1922. pointer-events: auto !important;
  1923. align-self: auto !important;
  1924. }
  1925. }
  1926. .to-right {
  1927. :deep(#LivePlayerBox) {
  1928. padding-right: 0px;
  1929. }
  1930. }
  1931. .to-no-right {
  1932. padding: 0 350px !important;
  1933. }
  1934. :deep(.button-box) {
  1935. position: relative;
  1936. padding: 5px;
  1937. // border: 1px transparent solid;
  1938. border-radius: 5px;
  1939. margin-left: 8px;
  1940. margin-right: 8px;
  1941. width: auto;
  1942. // height: 40px;
  1943. // border: 1px solid #65dbea;
  1944. height: 35px !important;
  1945. display: flex;
  1946. align-items: center;
  1947. justify-content: center;
  1948. color: var(--vent-font-color);
  1949. padding: 0 15px 5px 15px;
  1950. cursor: pointer;
  1951. &:hover {
  1952. background: var(--vent-device-manager-control-btn-hover);
  1953. }
  1954. &::before {
  1955. width: calc(100% - 6px);
  1956. height: 27px;
  1957. content: '';
  1958. position: absolute;
  1959. top: 3px;
  1960. right: 0;
  1961. left: 3px;
  1962. bottom: 0;
  1963. z-index: -1;
  1964. border-radius: inherit;
  1965. /*important*/
  1966. background: var(--vent-device-manager-control-btn);
  1967. }
  1968. &::after {
  1969. width: calc(100% + 32px);
  1970. height: 10px;
  1971. content: '';
  1972. position: absolute;
  1973. top: 28px;
  1974. right: 0;
  1975. left: -16px;
  1976. bottom: 0;
  1977. z-index: -1;
  1978. border-radius: inherit;
  1979. /*important*/
  1980. background-position: center;
  1981. background-size: 100%;
  1982. z-index: 999;
  1983. }
  1984. }
  1985. .video-icon {
  1986. width: 30px;
  1987. height: 38px;
  1988. position: absolute;
  1989. top: 580px;
  1990. right: 395px;
  1991. color: var(--vent-font-color);
  1992. z-index: 1;
  1993. font-size: 28px;
  1994. }
  1995. .no-play {
  1996. &::after {
  1997. position: absolute;
  1998. width: 80%;
  1999. height: 100%;
  2000. content: '';
  2001. left: 12%;
  2002. top: -5px;
  2003. background: linear-gradient(
  2004. to bottom left,
  2005. transparent 0%,
  2006. transparent calc(50% - 2px),
  2007. #ffffff 50%,
  2008. transparent calc(50% + 2px),
  2009. transparent 100%
  2010. );
  2011. }
  2012. }
  2013. </style>