HistoryTableChart.vue 16 KB

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