content.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. <!-- eslint-disable vue/multi-word-component-names -->
  2. <template>
  3. <!-- Header部分 -->
  4. <div v-if="headerConfig.show" class="w-100% flex content__header">
  5. <!-- 选择下拉框,自动填充剩余空间,这种实现是因为 Select 不支持 suffix -->
  6. <Dropdown
  7. v-if="headerConfig.showSelector"
  8. class="flex-grow-1 content__header_left"
  9. :trigger="['click']"
  10. :bordered="false"
  11. @open-change="headerVisible = $event"
  12. >
  13. <div class="w-100% flex flex-items-center" @click.prevent>
  14. <SwapOutlined class="w-30px" />
  15. <div class="flex-grow-1">
  16. {{ selectedDeviceLabel }}
  17. </div>
  18. <CaretUpOutlined class="w-30px" v-if="headerVisible" />
  19. <CaretDownOutlined class="w-30px" v-else />
  20. </div>
  21. <template #overlay>
  22. <Menu :selected-keys="[selectedDeviceID]" @click="headerSelectHandler">
  23. <MenuItem v-for="item in options" :key="item.value" :title="item.label">
  24. {{ item.label }}
  25. </MenuItem>
  26. </Menu>
  27. </template>
  28. </Dropdown>
  29. <template v-if="headerConfig.showSlot">
  30. <div class="flex flex-items-center flex-grow-1 content__header_right">
  31. <SwapOutlined class="w-30px" />
  32. <div class="flex-grow-1">
  33. {{ selectedDeviceSlot }}
  34. </div>
  35. </div>
  36. </template>
  37. </div>
  38. <!-- 主体内容部分 -->
  39. <div class="content" :class="{ content_without_header: !headerConfig.show }">
  40. <!-- 背景 -->
  41. <img v-if="background.show && background.type === 'image'" class="content__background" :src="background.link" />
  42. <video
  43. v-if="background.show && background.type === 'video'"
  44. class="content__background content__background_video"
  45. width="100%"
  46. autoplay
  47. loop
  48. muted
  49. >
  50. <source :src="background.link" />
  51. Not Supportted Link Or Browser
  52. </video>
  53. <template v-for="config in layoutConfig" :key="config.key">
  54. <!-- 告示板部分 -->
  55. <div v-if="config.key === 'board'" class="content__module flex flex-justify-around pt-10px pb-10px">
  56. <MiniBoard
  57. v-for="item in config.items"
  58. :key="item.prop"
  59. :label="item.label"
  60. :value="item.value"
  61. :type="config.type"
  62. :layout="config.layout"
  63. />
  64. </div>
  65. <!-- 图表部分,这部分通常需要填充,有告示板、Header等内容需要填充父级 -->
  66. <template v-if="config.key === 'chart'">
  67. <CustomChart class="content__module flex-grow" :chart-config="config.config" :chart-data="config.data" />
  68. </template>
  69. <!-- 通常列表部分 -->
  70. <template v-if="config.key === 'list'">
  71. <template v-if="config.type === 'timeline'">
  72. <TimelineList class="content__module" :list-config="config.items" />
  73. </template>
  74. <template v-else>
  75. <CustomList class="content__module" :type="config.type" :list-config="config.items" />
  76. </template>
  77. </template>
  78. <template v-if="config.key === 'gallery'">
  79. <CustomGallery class="content__module" :type="config.type" :gallery-config="config.items" />
  80. </template>
  81. <template v-if="config.key === 'complex_list'">
  82. <ComplexList class="content__module" :type="config.type" :list-config="config.items" :gallery-config="config.galleryItems" />
  83. </template>
  84. <!-- 表格部分,这部分通常是占一整个模块的 -->
  85. <template v-if="config.key === 'table'">
  86. <CommonTable
  87. v-if="config.type === 'A'"
  88. :columns="config.columns"
  89. :data="tableData"
  90. class="content__module text-center flex-grow overflow-auto"
  91. />
  92. <CustomTable
  93. v-else
  94. :type="config.type"
  95. :columns="config.columns"
  96. :data="tableData"
  97. class="content__module text-center flex-grow overflow-auto"
  98. />
  99. </template>
  100. <template v-if="config.key === 'blast_delta'">
  101. <BlastDelta class="content__module" :pos-monitor="blastDeltaData" :canvas-size="{ width: 250, height: 137 }" />
  102. </template>
  103. <template v-if="config.key === 'fire_control'">
  104. <FIreControl class="content__module" />
  105. </template>
  106. <template v-if="config.key === 'fire_warn'">
  107. <FIreWarn class="content__module" />
  108. </template>
  109. </template>
  110. </div>
  111. </template>
  112. <script lang="ts" setup>
  113. import { computed, onMounted, ref } from 'vue';
  114. import {
  115. Config,
  116. // ModuleDataBoard,
  117. // ModuleDataChart,
  118. // ModuleDataList,
  119. // ModuleDataPreset,
  120. // ModuleDataTable,
  121. } from '../../../deviceManager/configurationTable/types';
  122. import { useInitDevices } from '../hooks/useInit';
  123. import { MenuItem, Menu, Dropdown } from 'ant-design-vue';
  124. import { SwapOutlined, CaretUpOutlined, CaretDownOutlined } from '@ant-design/icons-vue';
  125. import MiniBoard from './MiniBoard.vue';
  126. import TimelineList from './TimelineList.vue';
  127. import CustomList from './CustomList.vue';
  128. import CustomGallery from './CustomGallery.vue';
  129. import ComplexList from './ComplexList.vue';
  130. import CustomTable from './CustomTable.vue';
  131. import { getFormattedText, getRawProp } from '../../../deviceManager/configurationTable/adapters';
  132. import CustomChart from './CustomChart.vue';
  133. import { get, clone } from 'lodash-es';
  134. import CommonTable from '../../billboard/components/CommonTable.vue';
  135. import BlastDelta from '../../../monitorManager/deviceMonitor/components/device/modal/blastDelta.vue';
  136. import FIreWarn from './FIreWarn.vue';
  137. import FIreControl from './FIreControl.vue';
  138. import { posMonitorData } from '../configurable.data';
  139. const props = defineProps<{
  140. deviceType: Config['deviceType'];
  141. moduleData: Config['moduleData'];
  142. showStyle: Config['showStyle'];
  143. }>();
  144. const { header: headerConfig, background, layout, mock } = props.moduleData;
  145. /** 根据配置里的layout将配置格式化为带 key 的具体配置,例如:[{ key: 'list', value: any, ...ModuleDataList }] */
  146. const layoutConfig = computed(() => {
  147. const refData = selectedDevice.value;
  148. const board = clone(props.moduleData.board);
  149. const list = clone(props.moduleData.list);
  150. const gallery = clone(props.moduleData.gallery);
  151. const complex_list = clone(props.moduleData.complex_list);
  152. const chart = clone(props.moduleData.chart);
  153. const table = clone(props.moduleData.table);
  154. const preset = clone(props.moduleData.preset);
  155. return layout.reduce((arr: any[], key) => {
  156. switch (key) {
  157. case 'board': {
  158. const cfg = board.shift();
  159. if (!cfg) break;
  160. const data = mock || cfg.readFrom ? get(refData, cfg.readFrom) : refData;
  161. arr.push({
  162. ...cfg,
  163. key,
  164. items: cfg.items.map((i) => {
  165. return {
  166. ...i,
  167. label: getFormattedText(data, i.label),
  168. value: getFormattedText(data, i.prop),
  169. };
  170. }),
  171. });
  172. break;
  173. }
  174. case 'list': {
  175. const cfg = list.shift();
  176. if (!cfg) break;
  177. const data = mock || cfg.readFrom ? get(refData, cfg.readFrom) : refData;
  178. arr.push({
  179. ...cfg,
  180. key,
  181. items: cfg.items.map((i) => {
  182. return {
  183. ...i,
  184. label: getFormattedText(data, i.label),
  185. value: getFormattedText(data, i.prop),
  186. };
  187. }),
  188. });
  189. break;
  190. }
  191. case 'gallery': {
  192. const cfg = gallery.shift();
  193. if (!cfg) break;
  194. const data = mock || cfg.readFrom ? get(refData, cfg.readFrom) : refData;
  195. arr.push({
  196. ...cfg,
  197. key,
  198. items: cfg.items.map((i) => {
  199. return {
  200. ...i,
  201. label: getFormattedText(data, i.label),
  202. value: getFormattedText(data, i.prop),
  203. };
  204. }),
  205. });
  206. break;
  207. }
  208. case 'complex_list': {
  209. const cfg = complex_list.shift();
  210. if (!cfg) break;
  211. const data = mock || cfg.readFrom ? get(refData, cfg.readFrom) : refData;
  212. arr.push({
  213. ...cfg,
  214. key,
  215. items: cfg.items.map((i) => {
  216. return {
  217. ...i,
  218. label: getFormattedText(data, i.label),
  219. value: getFormattedText(data, i.prop),
  220. };
  221. }),
  222. galleryItems: cfg.galleryItems.map((i) => {
  223. return {
  224. ...i,
  225. label: getFormattedText(data, i.label),
  226. value: getFormattedText(data, i.prop),
  227. };
  228. }),
  229. });
  230. break;
  231. }
  232. case 'chart': {
  233. const cfg = chart.shift();
  234. if (!cfg) break;
  235. const data = mock || cfg.readFrom ? get(refData, cfg.readFrom) : refData;
  236. arr.push({
  237. key,
  238. config: cfg,
  239. data: get(data, cfg.readFrom, []),
  240. });
  241. break;
  242. }
  243. case 'table': {
  244. const cfg = table.shift();
  245. if (!cfg) break;
  246. const data = mock || cfg.readFrom ? get(refData, cfg.readFrom) : refData;
  247. arr.push({
  248. ...cfg,
  249. key,
  250. columns: (cfg.columns || []).map((e) => {
  251. return {
  252. name: e.label,
  253. prop: getRawProp(e.prop),
  254. };
  255. }),
  256. data: get(data, cfg.readFrom, []),
  257. });
  258. break;
  259. }
  260. default: {
  261. const cfg = preset.shift();
  262. if (!cfg) break;
  263. const data = mock || cfg.readFrom ? get(refData, cfg.readFrom) : refData;
  264. arr.push({
  265. key,
  266. data,
  267. config: cfg,
  268. });
  269. break;
  270. }
  271. }
  272. return arr;
  273. }, []);
  274. });
  275. // 额外的 header 相关的变量
  276. const headerVisible = ref(false);
  277. function headerSelectHandler({ key }) {
  278. selectedDeviceID.value = key;
  279. }
  280. // 额外的告示牌相关的变量
  281. // const boardConfig = computed(() => {
  282. // const data = selectedDevice.value;
  283. // return (board || []).map((b) => {
  284. // return {
  285. // ...b.items,
  286. // value: getFormattedText(data, b.prop, b.formatter),
  287. // };
  288. // });
  289. // });
  290. // 额外的时间线列表相关的变量
  291. // const listConfig = computed(() => {
  292. // const data = selectedDevice.value;
  293. // return (list || []).map((b) => {
  294. // return {
  295. // ...b,
  296. // value: getFormattedText(data, b.prop, b.formatter),
  297. // };
  298. // });
  299. // });
  300. // const listType = computed(() => {
  301. // return list[0]?.type || 'A';
  302. // });
  303. // const chartConfig = computed(() => {
  304. // return chart[0];
  305. // });
  306. // const chartData = computed(() => {
  307. // const data = selectedDevice.value;
  308. // return get(data, chart[0]?.readFrom, []);
  309. // });
  310. // const tableConfig = computed(() => {
  311. // return {
  312. // columns: (table[0]?.columns || []).map((e) => {
  313. // return {
  314. // name: e.label,
  315. // prop: e.prop,
  316. // };
  317. // }),
  318. // };
  319. // });
  320. const tableData = computed(() => {
  321. // const data = selectedDevice.value;
  322. return [
  323. {
  324. index: '1',
  325. time: '2024/07/22 07:00',
  326. warn: '未知',
  327. cate: 'xxx综采工作面',
  328. temp: '26',
  329. wspd: '2',
  330. spst: 'ON',
  331. },
  332. {
  333. index: '2',
  334. time: '2024/07/22 08:00',
  335. warn: '未知',
  336. cate: 'xxx综采工作面',
  337. temp: '26',
  338. wspd: '2',
  339. spst: 'ON',
  340. },
  341. {
  342. index: '3',
  343. time: '2024/07/22 09:00',
  344. warn: '未知',
  345. cate: 'xxx综采工作面',
  346. temp: '26',
  347. wspd: '2',
  348. spst: 'ON',
  349. },
  350. {
  351. index: '4',
  352. time: '2024/07/22 10:00',
  353. warn: '未知',
  354. cate: 'xxx综采工作面',
  355. temp: '26',
  356. wspd: '2',
  357. spst: 'ON',
  358. },
  359. ];
  360. // return get(data, table[0]?.readFrom, []);
  361. });
  362. const blastDeltaData = ref();
  363. const { selectedDeviceID, selectedDevice, selectedDeviceSlot, selectedDeviceLabel, options, fetchDevices } = useInitDevices(
  364. props.deviceType,
  365. headerConfig
  366. );
  367. onMounted(() => {
  368. blastDeltaData.value = posMonitorData;
  369. fetchDevices();
  370. });
  371. </script>
  372. <style lang="less" scoped>
  373. @import '@/design/vent/color.less';
  374. /* Header 相关的样式 */
  375. .content__header {
  376. height: 30px;
  377. background-image: linear-gradient(90deg, #3df6ff44, transparent 20%, transparent 80%, #3df6ff44);
  378. }
  379. .content__header_left {
  380. border-left: 3px solid;
  381. border-right: 3px solid;
  382. border-image-source: linear-gradient(to top, #185f7188, #3df6ff, #185f7188);
  383. border-image-slice: 1;
  384. }
  385. .content__header_right {
  386. border-left: 3px solid;
  387. border-right: 3px solid;
  388. border-image-source: linear-gradient(to top, #185f7188, #3df6ff, #185f7188);
  389. border-image-slice: 1;
  390. min-width: 160px;
  391. }
  392. .content {
  393. height: calc(100% - 30px);
  394. position: relative;
  395. // z-index: -2;
  396. display: flex;
  397. flex-direction: column;
  398. }
  399. .content_without_header {
  400. height: 100%;
  401. }
  402. .content__background {
  403. width: 100%;
  404. height: 100%;
  405. position: absolute;
  406. top: 0;
  407. left: 0;
  408. z-index: -1;
  409. object-fit: fill;
  410. }
  411. .content__module {
  412. margin-top: 5px;
  413. margin-bottom: 5px;
  414. }
  415. .content__module:first-of-type {
  416. margin-top: 0;
  417. }
  418. .content__module:last-of-type {
  419. margin-bottom: 0;
  420. }
  421. ::v-deep .zxm-select:not(.zxm-select-customize-input) .zxm-select-selector {
  422. /* background-color: transparent; */
  423. color: #fff;
  424. }
  425. ::v-deep .zxm-select-arrow {
  426. color: #fff;
  427. }
  428. ::v-deep .zxm-select-selection-item {
  429. color: #fff !important;
  430. }
  431. ::v-deep .zxm-select-selection-placeholder {
  432. color: #fff !important;
  433. }
  434. </style>