BasicMonitoring.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <template>
  2. <!-- 场景选择下拉框 -->
  3. <customHeader
  4. :fieldNames="{ label: 'systemname', value: 'id', options: 'children' }"
  5. :options="options"
  6. :optionValue="optionValue"
  7. @change="changeSelectRow"
  8. >
  9. {{ mainTitle }}
  10. </customHeader>
  11. <!-- 默认插槽,一般用来放模型 -->
  12. <slot :monitor-data="monitorData"></slot>
  13. <div class="scene-box">
  14. <div class="center-container">
  15. <!-- 监测模块 -->
  16. <template v-if="activeKey == 'monitor'">
  17. <slot name="opration">
  18. <div class="button-wrapper">
  19. <template v-for="item in mainConfig.operations" :key="item.key">
  20. <template v-if="hasPermission(item.permission)">
  21. <div :class="{ 'button-box': true, 'button-box-disable': item.disabled }" @click="handleOperate(item)">{{ item.label }} </div>
  22. </template>
  23. </template>
  24. </div>
  25. </slot>
  26. <slot name="monitor">
  27. <ModuleCommon
  28. v-for="cfg in mainConfig.configs"
  29. :key="cfg.deviceType"
  30. :show-style="cfg.showStyle"
  31. :module-data="cfg.moduleData"
  32. :module-name="cfg.moduleName"
  33. :device-type="cfg.deviceType"
  34. :data="monitorData"
  35. :visible="true"
  36. />
  37. </slot>
  38. </template>
  39. <div v-else class="history-group">
  40. <!-- 场景下关联的设备有很多,在这里选择 -->
  41. <div v-if="showDeviceList && deviceList.length > 0" class="device-button-group">
  42. <div
  43. v-for="(device, index) in deviceList"
  44. :class="{ 'device-button': true, 'device-active': deviceActive == device.deviceType }"
  45. :key="index"
  46. @click="deviceChange(index)"
  47. >
  48. {{ device.deviceName }}
  49. </div>
  50. </div>
  51. <div class="history-container">
  52. <slot name="history" :device-type="deviceType" :device-id="optionValue">
  53. <HistoryTable
  54. v-if="activeKey == 'monitor_history'"
  55. class="vent-margin-t-20"
  56. :columns-type="deviceType"
  57. :device-type="deviceType"
  58. :sysId="optionValue"
  59. :scroll="{ y: 650 }"
  60. v-bind="monitorHistoryConfig"
  61. />
  62. </slot>
  63. <slot name="handler" :device-type="deviceType" :device-id="optionValue">
  64. <HandlerHistoryTable
  65. v-if="activeKey == 'handler_history'"
  66. class="vent-margin-t-20"
  67. columns-type="operator_history"
  68. :deviceType="deviceType"
  69. v-bind="handlerHistoryConfig"
  70. />
  71. </slot>
  72. <slot name="alarm" :device-type="deviceType" :device-id="optionValue">
  73. <AlarmHistoryTable
  74. v-if="activeKey == 'faultRecord'"
  75. columns-type="alarm"
  76. :device-type="deviceType"
  77. :sys-id="optionValue"
  78. v-bind="alarmHistoryConfig"
  79. />
  80. </slot>
  81. </div>
  82. </div>
  83. </div>
  84. <BottomMenu @change="changeActive" />
  85. </div>
  86. <PasswordModal
  87. :modal-is-show="passwordModalShown"
  88. :modal-title="operatingTarget?.label"
  89. @handle-ok="handlePasswordOK"
  90. @handle-cancel="passwordModalShown = false"
  91. />
  92. </template>
  93. <script setup lang="ts">
  94. import customHeader from '/@/components/vent/customHeader.vue';
  95. import { ref, onMounted, onUnmounted } from 'vue';
  96. import { getDevice, sysList } from '/@/views/vent/monitorManager/comment/comment.api';
  97. import BottomMenu from '/@/views/vent/comment/components/bottomMenu.vue';
  98. import ModuleCommon from '/@/views/vent/home/configurable/components/ModuleCommon.vue';
  99. import HistoryTable from '/@/views/vent/monitorManager/comment/HistoryTable.vue';
  100. import HandlerHistoryTable from '/@/views/vent/monitorManager/comment/HandlerHistoryTable.vue';
  101. import AlarmHistoryTable from '/@/views/vent/monitorManager/comment/AlarmHistoryTable.vue';
  102. import { useRouter } from 'vue-router';
  103. import { Config } from '/@/views/vent/deviceManager/configurationTable/types';
  104. import { message, Modal } from 'ant-design-vue';
  105. import _ from 'lodash';
  106. import PasswordModal from '/@/views/vent/monitorManager/comment/components/PasswordModal.vue';
  107. import { usePermission } from '/@/hooks/web/usePermission';
  108. import { deviceControlApi } from '/@/api/vent';
  109. type DeviceType = { deviceType: string; deviceName: string; datalist: any[] };
  110. type Operation = {
  111. label: string;
  112. key: string;
  113. value: string;
  114. disabled?: boolean;
  115. showPassword?: boolean;
  116. permission?: string;
  117. onOk?: (key: string, value: string, pwd?: string) => Promise<void>;
  118. };
  119. const props = withDefaults(
  120. defineProps<{
  121. mainTitle: string;
  122. /** 是否显示历史数据上方的设备类型选择器 */
  123. showDeviceList?: boolean;
  124. /** 请求场景数据传入的类型字符 */
  125. strtype: string;
  126. /** 请求场景数据传入的页面类型字符 */
  127. pagetype?: string;
  128. /** 获取各表格配置时依赖的设备类型 */
  129. // deviceType: string;
  130. /** 主要模块配置 */
  131. mainConfig: {
  132. operations: Operation[];
  133. configs: Config[];
  134. /** 获取该场景所含设备及其监测信息的API */
  135. monitorApi?: (params: { deviceType: string; deviceId: number | string }) => Promise<any>;
  136. /** 定时获取监测信息的配置,单位为毫秒,不传入即默认,传0即停用 */
  137. timer?: number;
  138. };
  139. /** 历史数据配置 */
  140. monitorHistoryConfig: {
  141. /** 请求历史数据时传入的类型字符 */
  142. columnsType?: string;
  143. /** 如果默认的设备类型不适用,可以传递固定的类型 */
  144. deviceType?: string;
  145. /** 仅展示已绑定设备,选择是则从系统中获取sysId下已绑定设备。仅能查询到已绑定设备的历史数据 */
  146. onlyBounedDevices?: boolean;
  147. /** 显示历史数据曲线图 */
  148. showHistoryCurve?: boolean;
  149. };
  150. /** 操作历史配置 */
  151. handlerHistoryConfig: {
  152. /** 请求操作历史时传入的类型字符 */
  153. columnsType?: string;
  154. /** 如果默认的设备类型不适用,可以传递固定的类型 */
  155. deviceType?: string;
  156. /** 获取操作历史的API,可以不提供以使用默认的请求 */
  157. deviceListApi?: (params: any) => Promise<any[]>;
  158. };
  159. /** 报警历史配置 */
  160. alarmHistoryConfig: {
  161. /** 请求报警历史时传入的类型字符 */
  162. columnsType?: string;
  163. /** 如果默认的设备类型不适用,可以传递固定的类型 */
  164. deviceType?: string;
  165. /** 获取报警历史的API,可以不提供以使用默认的请求 */
  166. list?: (params: any) => Promise<any[]>;
  167. /** 获取设备以供报警历史过滤的API,可以不提供以使用默认的请求 */
  168. deviceListApi?: (params: any) => Promise<any[]>;
  169. };
  170. }>(),
  171. {
  172. mainConfig: () => ({
  173. operations: [],
  174. configs: [],
  175. }),
  176. monitorHistoryConfig: () => ({}),
  177. handlerHistoryConfig: () => ({}),
  178. alarmHistoryConfig: () => ({}),
  179. pagetype: 'normal',
  180. showDeviceList: true,
  181. }
  182. );
  183. const { currentRoute } = useRouter();
  184. const { hasPermission } = usePermission();
  185. const activeKey = ref('monitor');
  186. function changeActive(activeValue) {
  187. activeKey.value = activeValue;
  188. }
  189. /** 场景选项 */
  190. const options = ref([]);
  191. /** 已选择了的场景的id */
  192. const optionValue = ref('');
  193. /** 获取左上角场景选择框数据的方法,如果此时初始场景未赋值则选择首项做初始化 */
  194. async function getSysDataSource() {
  195. const res = await sysList({ strtype: props.strtype, pagetype: props.pagetype }).catch(() => {
  196. message.error('获取场景数据时发生了错误');
  197. });
  198. // 初始时选择第一条数据
  199. options.value = res.records || [];
  200. if (!optionValue.value) {
  201. changeSelectRow(options.value[0]['id']);
  202. }
  203. }
  204. // 切换检测数据
  205. function changeSelectRow(deviceID) {
  206. optionValue.value = deviceID;
  207. getDeviceList();
  208. }
  209. /** 当前场景所关联设备 */
  210. const deviceList = ref<DeviceType[]>([]);
  211. const deviceActive = ref('');
  212. const deviceType = ref('');
  213. /** 选择设备 */
  214. function deviceChange(index) {
  215. deviceType.value = deviceList.value[index]?.deviceType || '';
  216. deviceActive.value = deviceList.value[index]?.deviceType || '';
  217. }
  218. /** 查询当前场景所关联设备列表 */
  219. async function getDeviceList() {
  220. const { msgTxt = [] } = await getDevice({ devicetype: 'sys', systemID: optionValue.value }).catch(() => {
  221. message.error('获取已绑定设备时发生了错误');
  222. });
  223. deviceList.value = msgTxt.reduce((arr, item) => {
  224. const data = item.datalist.map((data: any) => {
  225. return Object.assign(data, data.readData);
  226. });
  227. // sys代表场景本身,应该过滤掉去处理该场景下的关联设备
  228. if (item.type != 'sys') {
  229. arr.unshift({
  230. deviceType: item.type,
  231. deviceName: item.typeName || item.datalist[0].typeName,
  232. datalist: data,
  233. });
  234. }
  235. return arr;
  236. }, []);
  237. if (!deviceActive.value) {
  238. deviceChange(0);
  239. }
  240. }
  241. let timer: NodeJS.Timeout;
  242. /** 场景的监测数据 */
  243. const monitorData = ref<any>({});
  244. /** 获取本场景下所绑定的设备,将监测数据赋值 */
  245. async function getMonitor() {
  246. if (props.mainConfig.monitorApi) {
  247. monitorData.value = await props.mainConfig
  248. .monitorApi({
  249. deviceType: deviceType.value,
  250. deviceId: optionValue.value,
  251. })
  252. .catch(() => {
  253. message.error('获取已绑定设备时发生了错误');
  254. });
  255. } else if (optionValue.value) {
  256. const { msgTxt = [] } = await getDevice({ devicetype: 'sys', systemID: optionValue.value }).catch(() => {
  257. message.error('获取已绑定设备时发生了错误');
  258. });
  259. const temp = {};
  260. msgTxt.forEach((item) => {
  261. _.set(temp, item.type, item.datalist);
  262. });
  263. monitorData.value = temp;
  264. }
  265. }
  266. /** 密码提示框是否显示 */
  267. const passwordModalShown = ref(false);
  268. /** 下发操作时的目标配置 */
  269. const operatingTarget = ref<Operation>();
  270. /** 操作按钮点击后根据配置弹出确认框,初始化数据 */
  271. function handleOperate(item: Operation) {
  272. if (item.disabled) return;
  273. operatingTarget.value = item;
  274. if (item.showPassword) {
  275. passwordModalShown.value = true;
  276. } else {
  277. Modal.confirm({
  278. title: '操作确认',
  279. content: `确定要进行${operatingTarget.value.label}操作吗?`,
  280. iconType: 'info',
  281. onOk: () => handlePasswordOK(),
  282. });
  283. }
  284. }
  285. /** 密码输入后确认的回调 */
  286. function handlePasswordOK(pwd?: string) {
  287. if (!operatingTarget.value) return message.error('操作目标不存在');
  288. const { onOk = deviceControl, key, value } = operatingTarget.value;
  289. return onOk(key, value, pwd)
  290. .then(() => {
  291. passwordModalShown.value = false;
  292. })
  293. .finally(() => {
  294. operatingTarget.value = undefined;
  295. });
  296. }
  297. function deviceControl(key: string, value: string, pwd?: string) {
  298. return deviceControlApi({
  299. deviceid: optionValue.value,
  300. devicetype: deviceType.value,
  301. password: pwd,
  302. paramcode: key,
  303. value: value,
  304. })
  305. .then((r) => {
  306. if (!r.success) throw r.message || '操作失败';
  307. message.success('指令下发成功');
  308. })
  309. .catch((e) => {
  310. message.error(typeof e === 'string' ? e : '指令下发失败');
  311. });
  312. }
  313. onMounted(async () => {
  314. if (currentRoute.value && currentRoute.value['query'] && currentRoute.value['query']['id']) {
  315. optionValue.value = currentRoute.value['query']['id'] as string;
  316. }
  317. await getSysDataSource();
  318. if (props.mainConfig.timer !== 0) {
  319. timer = setInterval(() => {
  320. getMonitor();
  321. }, props.mainConfig.timer || 5000);
  322. } else {
  323. getMonitor();
  324. }
  325. });
  326. onUnmounted(() => {
  327. clearInterval(timer);
  328. });
  329. </script>
  330. <style lang="less" scoped>
  331. @import '/@/design/vent/modal.less';
  332. @ventSpace: zxm;
  333. .scene-box {
  334. --image-tab-group-bg: url('/@/assets/images/vent/tab-group-bg.png');
  335. margin-top: 40px;
  336. pointer-events: none;
  337. .history-group {
  338. margin-top: 80px;
  339. padding: 0 10px;
  340. .history-container {
  341. pointer-events: auto;
  342. background: #6195af1a;
  343. // width: 100%;
  344. border: 1px solid #00fffd22;
  345. padding: 10px 0;
  346. box-shadow: 0 0 20px #44b4ff33 inset;
  347. }
  348. }
  349. .device-button-group {
  350. // margin: 0 20px;
  351. padding: 0 10px;
  352. display: flex;
  353. pointer-events: auto;
  354. position: relative;
  355. &::after {
  356. position: absolute;
  357. content: '';
  358. width: 100%;
  359. height: 2px;
  360. top: 30px;
  361. left: -1px;
  362. border-bottom: 1px solid #0efcff;
  363. }
  364. .device-button {
  365. padding: 4px 15px;
  366. position: relative;
  367. display: flex;
  368. justify-content: center;
  369. align-items: center;
  370. font-size: 14px;
  371. color: #fff;
  372. cursor: pointer;
  373. margin: 0 3px;
  374. &::before {
  375. content: '';
  376. position: absolute;
  377. top: 0;
  378. right: 0;
  379. bottom: 0;
  380. left: 0;
  381. border: 1px solid #6176af;
  382. transform: skewX(-38deg);
  383. background-color: rgba(0, 77, 103, 85%);
  384. z-index: -1;
  385. }
  386. }
  387. .device-active {
  388. // color: #0efcff;
  389. &::before {
  390. border-color: #0efcff;
  391. box-shadow: 1px 1px 3px 1px #0efcff inset;
  392. }
  393. }
  394. }
  395. }
  396. .center-container {
  397. width: 100%;
  398. height: calc(100% - 150px);
  399. }
  400. .input-box {
  401. display: flex;
  402. align-items: center;
  403. padding-left: 10px;
  404. .input-title {
  405. color: #73e8fe;
  406. width: auto;
  407. }
  408. .@{ventSpace}-input-number {
  409. border-color: #ffffff88;
  410. }
  411. margin-right: 10px;
  412. }
  413. .button-wrapper {
  414. position: relative;
  415. top: 30px;
  416. left: 500px;
  417. display: flex;
  418. justify-content: flex-start;
  419. align-items: center;
  420. margin-top: 10px;
  421. width: 1165px !important;
  422. height: 75px;
  423. background: var(--image-tab-group-bg) no-repeat;
  424. background-image: 100% 100%;
  425. padding: 0 30px;
  426. z-index: 10;
  427. .button-box {
  428. margin: 0 10px;
  429. }
  430. }
  431. .button-box {
  432. position: relative;
  433. padding: 5px;
  434. border-radius: 5px;
  435. width: auto;
  436. height: 34px;
  437. border: 1px solid var(--vent-base-border);
  438. display: flex;
  439. align-items: center;
  440. justify-content: center;
  441. color: var(--vent-font-color);
  442. padding: 0 10px;
  443. cursor: pointer;
  444. pointer-events: all;
  445. &:hover {
  446. background: var(--vent-device-manager-control-btn-hover);
  447. }
  448. &::before {
  449. width: calc(100% - 6px);
  450. height: 26px;
  451. content: '';
  452. position: absolute;
  453. top: 3px;
  454. right: 0;
  455. left: 3px;
  456. bottom: 0;
  457. z-index: -1;
  458. border-radius: inherit;
  459. /*important*/
  460. background: var(--vent-device-manager-control-btn);
  461. }
  462. }
  463. .button-box-disable {
  464. cursor: not-allowed;
  465. border: 1px solid var(--vent-base-border);
  466. &:hover {
  467. background: none;
  468. }
  469. &:before {
  470. background: linear-gradient(#5897c299, #4a92a899);
  471. }
  472. }
  473. </style>