HistoryTableChart.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. <template>
  2. <div class="" v-if="loading">
  3. <div class="charts-container">
  4. <a-form
  5. class="form-container"
  6. :model="historyParams"
  7. layout="inline"
  8. :label-col="{ style: { width: '100px' } }"
  9. :wrapper-col="{ span: 8 }"
  10. autocomplete="off"
  11. @submit.prevent="getDataSource"
  12. >
  13. <a-form-item label="查询设备">
  14. <a-select v-model:value="historyParams.selectedDeviceId" style="width: 200px">
  15. <a-select-option v-for="d in deviceOptions" :key="d.label" :value="d.value">{{ d.label }}</a-select-option>
  16. </a-select>
  17. </a-form-item>
  18. <a-form-item label="间隔时间">
  19. <a-select v-model:value="historyParams.interval" style="width: 200px">
  20. <a-select-option v-for="[key, value] in Array.from(intervalMap)" :key="key" :value="key">
  21. {{ value }}
  22. </a-select-option>
  23. </a-select>
  24. </a-form-item>
  25. <a-form-item label="开始时间">
  26. <a-date-picker
  27. v-model:value="historyParams.startTime"
  28. style="width: 200px"
  29. show-time
  30. valueFormat="YYYY-MM-DD HH:mm:ss"
  31. placeholder="请选择开始时间"
  32. />
  33. </a-form-item>
  34. <a-form-item label="结束时间">
  35. <a-date-picker
  36. v-model:value="historyParams.endTime"
  37. style="width: 200px"
  38. show-time
  39. valueFormat="YYYY-MM-DD HH:mm:ss"
  40. placeholder="请选择结束时间"
  41. />
  42. </a-form-item>
  43. <a-form-item>
  44. <a-button type="primary" html-type="submit">查询</a-button>
  45. </a-form-item>
  46. </a-form>
  47. </div>
  48. <Pagination
  49. v-model:current="pagination.current"
  50. v-model:pageSize="pagination.pageSize"
  51. :total="pagination.total"
  52. size="small"
  53. @change="handlePageChange"
  54. style="position: absolute; z-index: 99; top: 53px; right: 30px"
  55. />
  56. <div class="history-chart">
  57. <BarAndLine
  58. :charts-columns="chartsColumns"
  59. chartsType="history"
  60. :option="{
  61. legend: {
  62. top: '5',
  63. },
  64. grid: {
  65. top: 50,
  66. left: 100,
  67. right: 100,
  68. bottom: 50,
  69. },
  70. }"
  71. :data-source="dataSource"
  72. height="290px"
  73. :x-axis-prop-type="stationType !== 'redis' ? 'ttime' : 'time'"
  74. />
  75. </div>
  76. </div>
  77. </template>
  78. <script lang="ts" setup>
  79. //ts语法
  80. import { watchEffect, ref, watch, nextTick, reactive, onMounted, computed } from 'vue';
  81. import { FormSchema } from '/@/components/Form/index';
  82. import { defHttp } from '/@/utils/http/axios';
  83. import { Pagination } from 'ant-design-vue';
  84. import dayjs from 'dayjs';
  85. import BarAndLine from '/@/components/chart/BarAndLine.vue';
  86. import { getDictItemsByCode } from '/@/utils/dict';
  87. import { get } from 'lodash-es';
  88. const props = defineProps({
  89. columnsType: {
  90. type: String,
  91. },
  92. columns: {
  93. type: Array,
  94. // required: true,
  95. default: () => [],
  96. },
  97. deviceType: {
  98. type: String,
  99. required: true,
  100. },
  101. deviceListApi: {
  102. type: Function,
  103. },
  104. deviceArr: {
  105. type: Array,
  106. // required: true,
  107. default: () => [],
  108. },
  109. designScope: {
  110. type: String,
  111. },
  112. sysId: {
  113. type: String,
  114. },
  115. deviceId: {
  116. type: String,
  117. },
  118. scroll: {
  119. type: Object,
  120. default: { y: 0 },
  121. },
  122. formSchemas: {
  123. type: Array<FormSchema>,
  124. default: () => [],
  125. },
  126. /** 仅展示已绑定设备,选择是则从系统中获取sysId下已绑定设备。仅能查询到已绑定设备的历史数据 */
  127. onlyBounedDevices: {
  128. type: Boolean,
  129. default: false,
  130. },
  131. showHistoryCurve: {
  132. type: Boolean,
  133. default: false,
  134. },
  135. });
  136. const getDeviceListApi = (params) => defHttp.post({ url: '/monitor/device', params });
  137. const historyTable = ref();
  138. const loading = ref(false);
  139. const stationType = ref('plc1');
  140. const dataSource = ref([]);
  141. const intervalMap = new Map([
  142. ['1', '1秒'],
  143. ['2', '5秒'],
  144. ['3', '10秒'],
  145. ['4', '30秒'],
  146. ['5', '1分钟'],
  147. ['6', '10分钟'],
  148. ['7', '30分钟'],
  149. ['8', '1小时'],
  150. ['9', '1天'],
  151. ]);
  152. const historyParams = reactive({
  153. startTime: dayjs().startOf('date').format('YYYY-MM-DD HH:mm:ss').toString(),
  154. endTime: dayjs().format('YYYY-MM-DD HH:mm:ss').toString(),
  155. skip: '8',
  156. interval: '1小时',
  157. selectedDeviceId: '',
  158. });
  159. const emit = defineEmits(['change']);
  160. const historyType = ref('');
  161. const deviceKide = ref('');
  162. let deviceOptions = ref([]);
  163. const deviceTypeStr = ref('');
  164. const deviceTypeName = ref('');
  165. const deviceType = ref('');
  166. const chartsColumns = ref([
  167. // {
  168. // legend: '压差',
  169. // seriesName: '(Pa)',
  170. // ymax: 100,
  171. // yname: 'Pa',
  172. // linetype: 'bar',
  173. // yaxispos: 'left',
  174. // color: '#37BCF2',
  175. // sort: 1,
  176. // xRotate: 0,
  177. // dataIndex: 'frontRearDP',
  178. // },
  179. {
  180. legend: '前门推力',
  181. seriesName: '',
  182. ymax: 100,
  183. yname: '前门推力',
  184. linetype: 'line',
  185. yaxispos: 'left',
  186. color: '#37BCF2',
  187. sort: 1,
  188. xRotate: 0,
  189. dataIndex: '1zdqfktl',
  190. },
  191. {
  192. legend: '后门推力',
  193. seriesName: '-',
  194. ymax: 100,
  195. yname: '后门推力',
  196. linetype: 'line',
  197. yaxispos: 'right',
  198. color: '#FC4327',
  199. sort: 2,
  200. xRotate: 0,
  201. dataIndex: '2zdqfktl',
  202. },
  203. ]);
  204. loading.value = true;
  205. const pagination = {
  206. current: 1,
  207. pageSize: 20,
  208. showQuickJumper: false,
  209. total: 0,
  210. };
  211. const selectedOption = computed<Record<string, any> | undefined>(() => {
  212. let idval: string | undefined = getForm()?.getFieldsValue()?.gdeviceids;
  213. if (VENT_PARAM.historyIsMultiple && idval) {
  214. const arr = idval.split(',');
  215. idval = arr[arr.length - 1];
  216. }
  217. return deviceOptions.value.find((e: any) => {
  218. return e.value === idval;
  219. });
  220. });
  221. watch(
  222. () => {
  223. return props.columnsType;
  224. },
  225. async (newVal) => {
  226. if (!newVal) return;
  227. deviceKide.value = newVal;
  228. if (historyTable.value) {
  229. getForm().resetFields();
  230. // getForm().updateSchema();
  231. // getForm();
  232. }
  233. dataSource.value = [];
  234. // const column = getTableHeaderColumns(newVal.includes('_history') ? newVal : newVal + '_history');
  235. // if (column && column.length < 1) {
  236. // const arr = newVal.split('_');
  237. // console.log('历史记录列表表头------------>', arr[0] + '_monitor');
  238. // columns.value = getTableHeaderColumns(arr[0] + '_history');
  239. // if (columns.value.length < 1) {
  240. // if (historyType.value) {
  241. // columns.value = getTableHeaderColumns(historyType.value + '_history');
  242. // }
  243. // }
  244. // } else {
  245. // columns.value = column;
  246. // }
  247. await getDeviceList();
  248. nextTick(() => {
  249. getDataSource();
  250. });
  251. if (historyTable.value) reload();
  252. },
  253. {
  254. immediate: true,
  255. }
  256. );
  257. watch(historyType, (type) => {
  258. if (!type) return;
  259. // if (historyTable.value) getForm().resetFields()
  260. // const column = getTableHeaderColumns(type.includes('_history') ? type : type + '_history');
  261. // if (column && column.length < 1) {
  262. // const arr = type.split('_');
  263. // columns.value = getTableHeaderColumns(arr[0] + '_history');
  264. // } else {
  265. // columns.value = column;
  266. // }
  267. // setColumns(columns.value);
  268. });
  269. const showCurve = ref(false);
  270. // 是否显示历史曲线,在devices_shows_history_curve字典里可以配置哪些设备类型需要显示曲线
  271. // 字典内的字段可以是前缀,例如fanlocal之于fanlocal_normal
  272. // 安全监控设备需要更多的配置,除去配置safetymonitor,还需要配置哪些安全监控设备需要曲线
  273. // 因此可以配置例如A1001的dataTypeName代码(可以查看真实数据参考)
  274. function calcShowCurveValue() {
  275. const historyCurveDicts = getDictItemsByCode('devices_shows_history_curve') || [];
  276. const findDict = (str) => historyCurveDicts.some(({ value }) => str.startsWith(value));
  277. if (!props.showHistoryCurve) return false;
  278. const dt = props.deviceType; // 依赖项
  279. if (!findDict(dt)) return false;
  280. if (!dt.startsWith('safetymonitor')) return true;
  281. // 和字典的设备类型匹配后,如果是安全监控设备,需要额外的匹配安全监控类型
  282. const dtns = get(selectedOption.value, 'readData.dataTypeName', ''); // 依赖项
  283. return findDict(dtns);
  284. }
  285. // const tableScroll = computed(() => {
  286. // if (props.scroll.y && showCurve.value) return { y: props.scroll.y - 450 };
  287. // if (props.scroll.y) return { y: props.scroll.y - 100 };
  288. // return {};
  289. // });
  290. watch(
  291. () => props.deviceId,
  292. async () => {
  293. // await getForm().setFieldsValue({});
  294. await getDeviceList();
  295. }
  296. );
  297. /** 获取可供查询历史数据的设备列表 */
  298. async function getDeviceList() {
  299. // if (props.deviceType.split('_')[1] && props.deviceType.split('_')[1] === 'history') return;
  300. let result;
  301. let response;
  302. if (props.onlyBounedDevices) {
  303. response = await getDeviceListApi({
  304. systemID: props.sysId,
  305. devicetype: 'sys',
  306. }).then(({ msgTxt }) => {
  307. return { msgTxt: msgTxt.filter((e) => e.type === props.deviceType) };
  308. });
  309. } else if (props.sysId) {
  310. response = await getDeviceListApi({
  311. sysId: props.sysId,
  312. devicetype: props.deviceType.startsWith('vehicle') ? 'location_normal' : props.deviceType,
  313. pageSize: 10000,
  314. });
  315. } else if (props.deviceListApi) {
  316. response = await props.deviceListApi();
  317. } else {
  318. response = await getDeviceListApi({ devicetype: props.deviceType, pageSize: 10000 });
  319. }
  320. // 处理不同格式的数据
  321. if (response['records'] && response['records'].length > 0) {
  322. result = response['records'];
  323. } else if (response['msgTxt'] && response['msgTxt'][0] && response['msgTxt'][0]['datalist']) {
  324. result = response['msgTxt'][0]['datalist'];
  325. }
  326. if (response['msgTxt'] && response['msgTxt'][0]) {
  327. deviceTypeName.value = response['msgTxt'][0]['typeName'];
  328. deviceType.value = response['msgTxt'][0]['type'];
  329. }
  330. if (result) {
  331. deviceOptions.value = [];
  332. deviceOptions.value = result.map((item, index) => {
  333. return {
  334. label: item['strinstallpos'],
  335. value: item['id'] || item['deviceID'],
  336. strtype: item['strtype'] || item['deviceType'],
  337. strinstallpos: item['strinstallpos'],
  338. devicekind: item['devicekind'],
  339. stationtype: item['stationtype'],
  340. readData: item['readData'],
  341. };
  342. });
  343. stationType.value = deviceOptions.value[0]['stationtype'];
  344. if (props.deviceType.startsWith('vehicle')) {
  345. historyType.value = 'vehicle';
  346. } else {
  347. historyType.value = deviceOptions.value[0]['strtype'] || deviceOptions.value[0]['devicekind'];
  348. }
  349. /** 此处使用nextTick是由于可能表单暂未更新,而下面的方法依赖表单项 */
  350. nextTick(() => {
  351. showCurve.value = calcShowCurveValue();
  352. // initHistoryCurveColumns();
  353. });
  354. }
  355. if (VENT_PARAM.historyIsMultiple) {
  356. // await getForm().setFieldsValue({
  357. // gdeviceids: [props.deviceId ? props.deviceId : deviceOptions.value[0] ? deviceOptions.value[0]['value'] : ''],
  358. // });
  359. // await getForm().updateSchema({
  360. // field: 'gdeviceids',
  361. // componentProps: {
  362. // mode: 'multiple',
  363. // maxTagCount: 'responsive',
  364. // },
  365. // });
  366. } else {
  367. // await getForm().setFieldsValue({
  368. // gdeviceids: props.deviceId ? props.deviceId : deviceOptions.value[0] ? deviceOptions.value[0]['value'] : '',
  369. // });
  370. // await getForm().updateSchema({
  371. // field: 'gdeviceids',
  372. // });
  373. }
  374. }
  375. function handlePageChange(page, size) {
  376. console.log('当前页:', page, '每页条数:', size);
  377. // 此处发起数据请求
  378. pagination.current = page;
  379. pagination.pageSize = size;
  380. getDataSource();
  381. }
  382. function resetFormParam() {
  383. const formData = {};
  384. formData['pageNo'] = pagination.current;
  385. formData['pageSize'] = pagination.pageSize;
  386. formData['ttime_begin'] = historyParams.startTime;
  387. formData['ttime_end'] = historyParams.endTime;
  388. formData['column'] = 'createTime';
  389. formData['gdeviceids'] = props.deviceId ? props.deviceId : deviceOptions.value[0] ? deviceOptions.value[0]['value'] : '';
  390. if (stationType.value !== 'redis' && deviceOptions.value[0]) {
  391. formData['strtype'] = deviceTypeStr.value
  392. ? deviceTypeStr.value
  393. : deviceOptions.value[0]['strtype']
  394. ? deviceOptions.value[0]['strtype']
  395. : props.deviceType + '*';
  396. if (props.sysId) {
  397. formData['sysId'] = props.sysId;
  398. }
  399. return formData;
  400. } else {
  401. const params = {
  402. pageNum: pagination['current'],
  403. pageSize: pagination['pageSize'],
  404. column: pagination['createTime'],
  405. startTime: formData['ttime_begin'],
  406. endTime: formData['ttime_end'],
  407. deviceId: formData['gdeviceids'],
  408. strtype: props.deviceType + '*',
  409. sysId: props.sysId,
  410. interval: intervalMap.get(formData['skip']) ? intervalMap.get(formData['skip']) : '1h',
  411. isEmployee: props.deviceType.startsWith('vehicle') ? false : true,
  412. };
  413. return params;
  414. }
  415. }
  416. async function getDataSource() {
  417. dataSource.value = [];
  418. const params = await resetFormParam();
  419. if (stationType.value !== 'redis') {
  420. const result = await defHttp.get({ url: '/safety/ventanalyMonitorData/listdays', params: params });
  421. pagination.total = Math.abs(result['datalist']['total']) || 0;
  422. if (result['datalist']['records'].length > 0) {
  423. dataSource.value = result['datalist']['records'].map((item: any) => {
  424. return Object.assign(item, item['readData']);
  425. });
  426. } else {
  427. dataSource.value = [];
  428. }
  429. } else {
  430. const result = await defHttp.post({ url: '/monitor/history/getHistoryData', params: params });
  431. pagination.total = Math.abs(result['total']) || 0;
  432. dataSource.value = result['records'] || [];
  433. }
  434. }
  435. watchEffect(() => {
  436. if (historyTable.value && dataSource) {
  437. const data = dataSource.value || [];
  438. emit('change', data);
  439. }
  440. });
  441. onMounted(async () => {
  442. await getDeviceList();
  443. console.log(deviceOptions.value, 'abacaca');
  444. if (deviceOptions.value[0]) {
  445. nextTick(async () => {
  446. await getDataSource();
  447. });
  448. }
  449. });
  450. </script>
  451. <style scoped lang="less">
  452. @import '/@/design/theme.less';
  453. :deep(.@{ventSpace}-table-body) {
  454. height: auto !important;
  455. }
  456. :deep(.zxm-picker) {
  457. height: 30px !important;
  458. }
  459. .history-table {
  460. width: 100%;
  461. :deep(.jeecg-basic-table-form-container) {
  462. .@{ventSpace}-form {
  463. padding: 0 !important;
  464. border: none !important;
  465. margin-bottom: 0 !important;
  466. .@{ventSpace}-picker,
  467. .@{ventSpace}-select-selector {
  468. width: 100% !important;
  469. height: 100%;
  470. background: #00000017;
  471. border: 1px solid #b7b7b7;
  472. input,
  473. .@{ventSpace}-select-selection-item,
  474. .@{ventSpace}-picker-suffix {
  475. color: #fff;
  476. }
  477. .@{ventSpace}-select-selection-placeholder {
  478. color: #ffffffaa;
  479. }
  480. }
  481. }
  482. .@{ventSpace}-table-title {
  483. min-height: 0 !important;
  484. }
  485. }
  486. .pagination-box {
  487. display: flex;
  488. justify-content: flex-end;
  489. align-items: center;
  490. .page-num {
  491. border: 1px solid #0090d8;
  492. padding: 4px 8px;
  493. margin-right: 5px;
  494. color: #0090d8;
  495. }
  496. .btn {
  497. margin-right: 10px;
  498. }
  499. }
  500. }
  501. .history-chart {
  502. background-color: #0090d822;
  503. margin: 0 10px;
  504. }
  505. .charts-container {
  506. --image-no-camera_bg: url('/@/assets/images/vent/no-data.png');
  507. position: relative;
  508. height: 100%;
  509. .form-container {
  510. display: flex;
  511. // justify-content: center;
  512. margin-top: 10px !important;
  513. margin-bottom: 10px !important;
  514. }
  515. .charts-box {
  516. width: 100%;
  517. height: 100%;
  518. position: absolute;
  519. bottom: 0;
  520. top: 0px;
  521. }
  522. .@{ventSpace}-picker,
  523. .@{ventSpace}-select-selector {
  524. background: #00000017 !important;
  525. border: 1px solid @vent-form-item-border !important;
  526. input,
  527. .@{ventSpace}-select-selection-item,
  528. .@{ventSpace}-picker-suffix {
  529. color: #fff !important;
  530. }
  531. .@{ventSpace}-select-selection-placeholder {
  532. color: #b7b7b7 !important;
  533. }
  534. }
  535. .@{ventSpace}-select-arrow,
  536. .@{ventSpace}-picker-separator {
  537. color: #fff !important;
  538. }
  539. }
  540. .no-data {
  541. width: 100%;
  542. height: 475px;
  543. padding-top: 80px;
  544. background: var(--image-no-camera_bg) no-repeat;
  545. background-position: center;
  546. display: flex;
  547. justify-content: center;
  548. font-size: 50px;
  549. color: var(--vent-text-base);
  550. }
  551. :deep(.@{ventSpace}-select-dropdown) {
  552. color: #000 !important;
  553. .@{ventSpace}-select-item {
  554. color: #000 !important;
  555. }
  556. }
  557. .zxm-form-inline .zxm-form-item > .zxm-form-item-label > label {
  558. color: #fff !important;
  559. }
  560. .page-info {
  561. width: 100%;
  562. height: 100%;
  563. }
  564. </style>