DevWorkParamData.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. <script setup lang="ts">
  2. import { computed, ref, watch } from 'vue';
  3. import VChart from 'vue-echarts';
  4. import dayjs from 'dayjs';
  5. import { LineChart } from 'echarts/charts';
  6. import {
  7. AxisPointerComponent,
  8. DatasetComponent,
  9. DataZoomComponent,
  10. GridComponent,
  11. LegendComponent,
  12. MarkLineComponent,
  13. TooltipComponent,
  14. } from 'echarts/components';
  15. import { use } from 'echarts/core';
  16. import { CanvasRenderer } from 'echarts/renderers';
  17. import SvgIcon from '@/components/SvgIcon.vue';
  18. import { useRequest } from '@/hooks/request';
  19. import { useViewVisible } from '@/hooks/view-visible';
  20. import { t } from '@/i18n';
  21. import { getDevWorkHistoryData } from '@/api';
  22. import { getEChartsColors, timeSorter } from '@/utils';
  23. import { TimeRangePreset } from '@/constants';
  24. import type { Dayjs } from 'dayjs';
  25. import type { LineSeriesOption } from 'echarts/charts';
  26. import type {
  27. DatasetComponentOption,
  28. DataZoomComponentOption,
  29. GridComponentOption,
  30. LegendComponentOption,
  31. TooltipComponentOption,
  32. } from 'echarts/components';
  33. import type { ComposeOption } from 'echarts/core';
  34. import type { DevWorkHistoryDataItem, OptionItem, RangeValue } from '@/types';
  35. use([
  36. CanvasRenderer,
  37. AxisPointerComponent,
  38. MarkLineComponent,
  39. LegendComponent,
  40. TooltipComponent,
  41. DatasetComponent,
  42. GridComponent,
  43. LineChart,
  44. DataZoomComponent,
  45. ]);
  46. type EChartsOption = ComposeOption<
  47. | LegendComponentOption
  48. | TooltipComponentOption
  49. | DatasetComponentOption
  50. | GridComponentOption
  51. | LineSeriesOption
  52. | DataZoomComponentOption
  53. >;
  54. interface Props {
  55. devId: number;
  56. paramCodes: string[];
  57. }
  58. const props = defineProps<Props>();
  59. const { visible, showView, hideView } = useViewVisible();
  60. const { isLoading, handleRequest } = useRequest();
  61. const timeRange = ref<RangeValue>([dayjs().add(-1, 'd'), dayjs()]);
  62. const currTimeRangePreset = ref<TimeRangePreset>(TimeRangePreset.recent1Day);
  63. const timeRangePresets = computed<OptionItem<TimeRangePreset>[]>(() => {
  64. return [
  65. { value: TimeRangePreset.recent1Day, label: t('common.recent1Day') },
  66. { value: TimeRangePreset.recent7Day, label: t('common.recent7Day') },
  67. { value: TimeRangePreset.recent30Day, label: t('common.recent30Day') },
  68. { value: TimeRangePreset.custom, label: t('common.custom') },
  69. ];
  70. });
  71. const disabledDate = (current: Dayjs) => {
  72. return current && current > dayjs().endOf('day');
  73. };
  74. const setTimeRange = () => {
  75. switch (currTimeRangePreset.value) {
  76. case TimeRangePreset.recent1Day:
  77. timeRange.value = [dayjs().add(-1, 'd'), dayjs()];
  78. break;
  79. case TimeRangePreset.recent7Day:
  80. timeRange.value = [dayjs().add(-7, 'd'), dayjs()];
  81. break;
  82. case TimeRangePreset.recent30Day:
  83. timeRange.value = [dayjs().add(-30, 'd'), dayjs()];
  84. break;
  85. }
  86. };
  87. const handleRangePresetChange = () => {
  88. setTimeRange();
  89. if (currTimeRangePreset.value !== TimeRangePreset.custom) {
  90. getParamHistoryData();
  91. }
  92. };
  93. const handleTimeRangeChange = () => {
  94. currTimeRangePreset.value = TimeRangePreset.custom;
  95. getParamHistoryData();
  96. };
  97. watch(visible, () => {
  98. if (visible.value) {
  99. getParamHistoryData();
  100. }
  101. });
  102. const historyData = ref<DevWorkHistoryDataItem[]>([]);
  103. const paramLabel = computed<string>(() => {
  104. if (historyData.value.length) {
  105. const { deviceParamName, unit } = historyData.value[0];
  106. return `${deviceParamName}(${unit})`;
  107. }
  108. return '';
  109. });
  110. const option = computed<EChartsOption>(() => {
  111. const unitText = historyData.value[0]?.unit ?? '';
  112. const source = convertToEChartsDataset();
  113. const series = source[0].slice(1).map((name) => ({
  114. type: 'line',
  115. seriesLayoutBy: 'column',
  116. name,
  117. showSymbol: false,
  118. })) as LineSeriesOption;
  119. return {
  120. color: getEChartsColors(1),
  121. legend: {
  122. show: false,
  123. },
  124. tooltip: {
  125. show: true,
  126. trigger: 'axis',
  127. formatter(params) {
  128. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  129. const tempParms = params as any[];
  130. const time = dayjs(tempParms[0].axisValue).format('MM-DD HH:mm');
  131. let tableHtml = '<table class="echarts-tooltip-electricity">';
  132. tableHtml += `<tr><th>${time}</th></tr>`;
  133. tempParms.forEach((param) => {
  134. tableHtml += `<tr><td>${param.marker}${param.seriesName}</td><td>${param.value[1] ?? '-'}${unitText}</td></tr>`;
  135. });
  136. tableHtml += '</table>';
  137. return tableHtml;
  138. },
  139. },
  140. dataset: {
  141. source,
  142. },
  143. xAxis: {
  144. type: 'category',
  145. axisLine: {
  146. lineStyle: {
  147. type: 'dashed',
  148. color: '#EBEBEB',
  149. },
  150. },
  151. axisLabel: {
  152. color: '#999',
  153. fontSize: 10,
  154. formatter(value) {
  155. return dayjs(value).format('MM-DD HH:mm');
  156. },
  157. },
  158. axisTick: {
  159. show: false,
  160. },
  161. },
  162. yAxis: {
  163. axisLabel: {
  164. color: '#999',
  165. fontSize: 10,
  166. },
  167. splitLine: {
  168. lineStyle: {
  169. type: 'dashed',
  170. color: '#EBEBEB',
  171. },
  172. },
  173. },
  174. series,
  175. grid: {
  176. top: 10,
  177. right: 30,
  178. bottom: 90,
  179. left: 30,
  180. containLabel: true,
  181. },
  182. dataZoom: [
  183. {
  184. type: 'inside',
  185. start: 0,
  186. end: 100,
  187. bottom: 35,
  188. handleStyle: {
  189. label: { show: false },
  190. },
  191. showDetail: false,
  192. },
  193. {
  194. start: 0,
  195. end: 100,
  196. bottom: 35,
  197. handleStyle: {
  198. label: { show: false },
  199. },
  200. showDetail: false,
  201. },
  202. ],
  203. };
  204. });
  205. const getParamHistoryData = () => {
  206. handleRequest(async () => {
  207. const [startTime, endTime] = timeRange.value;
  208. const data = await getDevWorkHistoryData({
  209. deviceIds: [props.devId],
  210. deviceParamCode: props.paramCodes,
  211. startTime: startTime.format('YYYY-MM-DD HH:mm:ss'),
  212. endTime: endTime.format('YYYY-MM-DD HH:mm:ss'),
  213. });
  214. if (data.length) {
  215. historyData.value = data[0].hisVOS;
  216. } else {
  217. historyData.value = [];
  218. }
  219. });
  220. };
  221. type EChartsDatasetItemValue = string | number | null;
  222. const convertToEChartsDataset = () => {
  223. const times: string[] = [];
  224. const dataMap: Record<string, { time: string; value: string | number }[]> = {};
  225. historyData.value.forEach((item) => {
  226. const paramName = item.deviceParamName;
  227. dataMap[paramName] = [];
  228. item.value.forEach((val) => {
  229. if (!times.includes(val.time)) {
  230. times.push(val.time);
  231. }
  232. dataMap[paramName].push({
  233. time: val.time,
  234. value: val.value,
  235. });
  236. });
  237. });
  238. const source: EChartsDatasetItemValue[][] = [['time', ...Object.keys(dataMap)]];
  239. times.sort(timeSorter);
  240. times.forEach((time) => {
  241. const row: EChartsDatasetItemValue[] = [time];
  242. Object.values(dataMap).forEach((values) => {
  243. const found = values.find((v) => v.time === time);
  244. row.push(found ? found.value : null);
  245. });
  246. source.push(row);
  247. });
  248. return source;
  249. };
  250. const handleClose = () => {
  251. historyData.value = [];
  252. currTimeRangePreset.value = TimeRangePreset.recent1Day;
  253. setTimeRange();
  254. };
  255. defineExpose({
  256. showView,
  257. hideView,
  258. });
  259. </script>
  260. <template>
  261. <AModal
  262. v-model:open="visible"
  263. wrap-class-name="dev-work-param-data-modal"
  264. :title="$t('deviceWorkStatus.viewData')"
  265. :width="920"
  266. :z-index="1001"
  267. centered
  268. :footer="null"
  269. :after-close="handleClose"
  270. >
  271. <ASpin class="center-loading" :spinning="isLoading" />
  272. <div class="param-data-select">
  273. <span class="param-data-label">{{ $t('common.selectTime') }}</span>
  274. <ASelect v-model:value="currTimeRangePreset" class="param-preset-picker" @change="handleRangePresetChange">
  275. <ASelectOption v-for="item in timeRangePresets" :key="item.value" :value="item.value">
  276. {{ item.label }}
  277. </ASelectOption>
  278. </ASelect>
  279. <ARangePicker
  280. class="param-date-picker"
  281. v-model:value="timeRange"
  282. :allow-clear="false"
  283. :separator="$t('common.to')"
  284. :disabled-date="disabledDate"
  285. @change="handleTimeRangeChange"
  286. >
  287. <template #suffixIcon>
  288. <SvgIcon name="calendar" color="#333" />
  289. </template>
  290. </ARangePicker>
  291. </div>
  292. <div class="param-name-unit">{{ paramLabel }}</div>
  293. <VChart class="param-data-chart" :option="option" />
  294. </AModal>
  295. </template>
  296. <style lang="scss">
  297. .dev-work-param-data-modal {
  298. .ant-modal,
  299. .ant-modal > div,
  300. .ant-modal-content {
  301. height: 498px;
  302. }
  303. .ant-modal-content {
  304. display: flex;
  305. flex-direction: column;
  306. overflow: hidden;
  307. }
  308. .ant-modal-body {
  309. display: flex;
  310. flex-direction: column;
  311. height: calc(100% - 32px);
  312. }
  313. }
  314. </style>
  315. <style lang="scss" scoped>
  316. .param-data-select {
  317. margin-bottom: 16px;
  318. }
  319. .param-data-label {
  320. font-size: 14px;
  321. line-height: 24px;
  322. color: #333;
  323. }
  324. .param-preset-picker {
  325. width: 110px;
  326. margin-inline: 12px;
  327. }
  328. .param-date-picker {
  329. width: 256px;
  330. :deep(.ant-picker-range-separator) {
  331. padding-right: 16px;
  332. }
  333. }
  334. .param-name-unit {
  335. height: 17px;
  336. margin-left: 24px;
  337. font-size: 12px;
  338. line-height: 17px;
  339. color: #999;
  340. }
  341. .param-data-chart {
  342. height: 100%;
  343. }
  344. </style>