Commit 39ed06d6535485f9e77e8dcd85bd9983590bd4f0
1 parent
7b757491
feat: device list page drawer add history data panel
Showing
7 changed files
with
251 additions
and
15 deletions
src/hooks/component/useGridLayout.ts
0 → 100644
1 | +import type { ColProps } from 'ant-design-vue'; | |
2 | + | |
3 | +export function useGridLayout(all: number): ColProps; | |
4 | +export function useGridLayout(xs: number, lg: number, xxl: number): ColProps; | |
5 | +export function useGridLayout( | |
6 | + xs: number, | |
7 | + sm: number, | |
8 | + md: number, | |
9 | + lg: number, | |
10 | + xl: number, | |
11 | + xxl: number | |
12 | +): ColProps; | |
13 | +export function useGridLayout( | |
14 | + xs: number, | |
15 | + sm?: number, | |
16 | + md?: number, | |
17 | + lg?: number, | |
18 | + xl?: number, | |
19 | + xxl?: number | |
20 | +): ColProps { | |
21 | + const length = arguments.length; | |
22 | + const maxCol = 24; | |
23 | + | |
24 | + const toCol = (number: number) => { | |
25 | + return Math.floor(maxCol / number); | |
26 | + }; | |
27 | + | |
28 | + switch (length) { | |
29 | + case 1: | |
30 | + return { span: toCol(xs) }; | |
31 | + case 3: | |
32 | + return { xs: toCol(xs), lg: toCol(sm!), xxl: toCol(md!) }; | |
33 | + case 6: | |
34 | + return { | |
35 | + xs: toCol(xs), | |
36 | + sm: toCol(sm!), | |
37 | + md: toCol(md!), | |
38 | + lg: toCol(lg!), | |
39 | + xl: toCol(xl!), | |
40 | + xxl: toCol(xxl!), | |
41 | + }; | |
42 | + default: | |
43 | + return { span: toCol(xs) ?? 6 }; | |
44 | + } | |
45 | +} | ... | ... |
... | ... | @@ -46,7 +46,8 @@ export const AccountDetail: AppRouteRecordRaw = { |
46 | 46 | { |
47 | 47 | path: 'account_detail/:id', |
48 | 48 | name: 'Account_Detail', |
49 | - component: () => import('/@/views/system/account/AccountDetail.vue'), | |
49 | + // component: () => import('/@/views/system/account/AccountDetail.vue'), | |
50 | + component: () => import('../../views/alarm/config/ContactDrawer.vue'), | |
50 | 51 | meta: { |
51 | 52 | title: '用户详情', |
52 | 53 | }, | ... | ... |
... | ... | @@ -15,6 +15,9 @@ |
15 | 15 | <TabPane key="2" tab="实时数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> |
16 | 16 | <RealTimeData :deviceDetail="deviceDetail" /> |
17 | 17 | </TabPane> |
18 | + <TabPane key="7" tab="历史数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> | |
19 | + <HistoryData :deviceDetail="deviceDetail" /> | |
20 | + </TabPane> | |
18 | 21 | <TabPane key="5" tab="命令下发" v-if="deviceDetail?.deviceType !== 'SENSOR'"> |
19 | 22 | <CommandIssuance :deviceDetail="deviceDetail" /> |
20 | 23 | </TabPane> |
... | ... | @@ -46,6 +49,7 @@ |
46 | 49 | import TBoxDetail from '../tabs/TBoxDetail.vue'; |
47 | 50 | import CommandIssuance from '../tabs/CommandIssuance.vue'; |
48 | 51 | import { getDeviceDetail } from '/@/api/device/deviceManager'; |
52 | + import HistoryData from '../tabs/HistoryData.vue'; | |
49 | 53 | export default defineComponent({ |
50 | 54 | name: 'DeviceModal', |
51 | 55 | components: { |
... | ... | @@ -58,6 +62,7 @@ |
58 | 62 | ChildDevice, |
59 | 63 | CommandIssuance, |
60 | 64 | TBoxDetail, |
65 | + HistoryData, | |
61 | 66 | }, |
62 | 67 | emits: ['reload', 'register'], |
63 | 68 | setup() { | ... | ... |
1 | +<script lang="ts" setup> | |
2 | + import moment from 'moment'; | |
3 | + import { nextTick, onMounted, onUnmounted, Ref, ref, unref } from 'vue'; | |
4 | + import { getDeviceDataKeys, getDeviceHistoryInfo } from '/@/api/alarm/position'; | |
5 | + import { Empty } from 'ant-design-vue'; | |
6 | + import { useECharts } from '/@/hooks/web/useECharts'; | |
7 | + import { dateUtil } from '/@/utils/dateUtil'; | |
8 | + import { | |
9 | + AggregateDataEnum, | |
10 | + eChartOptions, | |
11 | + selectDeviceAttrSchema, | |
12 | + } from '/@/views/device/localtion/config.data'; | |
13 | + import { useTimePeriodForm } from '/@/views/device/localtion/cpns/TimePeriodForm'; | |
14 | + import { defaultSchemas, QueryWay } from '/@/views/device/localtion/cpns/TimePeriodForm/config'; | |
15 | + import TimePeriodForm from '/@/views/device/localtion/cpns/TimePeriodForm/TimePeriodForm.vue'; | |
16 | + import { SchemaFiled } from '/@/views/report/config/config.data'; | |
17 | + import { useGridLayout } from '/@/hooks/component/useGridLayout'; | |
18 | + import { ColEx } from '/@/components/Form/src/types'; | |
19 | + | |
20 | + interface DeviceDetail { | |
21 | + tbDeviceId: string; | |
22 | + } | |
23 | + | |
24 | + const props = defineProps<{ | |
25 | + deviceDetail: DeviceDetail; | |
26 | + }>(); | |
27 | + | |
28 | + const chartRef = ref(); | |
29 | + | |
30 | + const deviceAttrs = ref<string[]>([]); | |
31 | + | |
32 | + const loading = ref(false); | |
33 | + | |
34 | + const isNull = ref(false); | |
35 | + | |
36 | + function getSearchParams(value: Recordable) { | |
37 | + const { startTs, endTs, interval, agg, limit, keys, way } = value; | |
38 | + if (way === QueryWay.LATEST) { | |
39 | + return { | |
40 | + entityId: props.deviceDetail.tbDeviceId, | |
41 | + keys: keys ? keys : unref(deviceAttrs).join(), | |
42 | + startTs: moment().subtract(startTs, 'ms').valueOf(), | |
43 | + endTs: Date.now(), | |
44 | + interval, | |
45 | + agg, | |
46 | + limit, | |
47 | + }; | |
48 | + } else { | |
49 | + return { | |
50 | + entityId: props.deviceDetail.tbDeviceId, | |
51 | + keys: keys ? keys : unref(deviceAttrs).join(), | |
52 | + startTs: moment(startTs).valueOf(), | |
53 | + endTs: moment(endTs).valueOf(), | |
54 | + interval, | |
55 | + agg, | |
56 | + limit, | |
57 | + }; | |
58 | + } | |
59 | + } | |
60 | + | |
61 | + function hasDeviceAttr() { | |
62 | + if (!unref(deviceAttrs).length) { | |
63 | + return false; | |
64 | + } else { | |
65 | + return true; | |
66 | + } | |
67 | + } | |
68 | + | |
69 | + const { setOptions, getInstance } = useECharts(chartRef as Ref<HTMLDivElement>); | |
70 | + | |
71 | + function setChartOptions(data, keys?) { | |
72 | + const dataArray: any[] = []; | |
73 | + for (const key in data) { | |
74 | + for (const item1 of data[key]) { | |
75 | + let { ts, value } = item1; | |
76 | + const time = dateUtil(ts).format('YYYY-MM-DD HH:mm:ss'); | |
77 | + value = Number(value).toFixed(2); | |
78 | + dataArray.push([time, value, key]); | |
79 | + } | |
80 | + } | |
81 | + keys = keys ? [keys] : unref(deviceAttrs); | |
82 | + const series: any = keys.map((item) => { | |
83 | + return { | |
84 | + name: item, | |
85 | + type: 'line', | |
86 | + data: dataArray.filter((item1) => item1[2] === item), | |
87 | + }; | |
88 | + }); | |
89 | + // 设置数据 | |
90 | + setOptions(eChartOptions(series, keys)); | |
91 | + } | |
92 | + | |
93 | + const [register, method] = useTimePeriodForm({ | |
94 | + schemas: [...defaultSchemas, ...selectDeviceAttrSchema], | |
95 | + baseColProps: useGridLayout(2, 3, 4) as unknown as ColEx, | |
96 | + async submitFunc() { | |
97 | + // 表单验证 | |
98 | + await method.validate(); | |
99 | + const value = method.getFieldsValue(); | |
100 | + const searchParams = getSearchParams(value); | |
101 | + | |
102 | + if (!hasDeviceAttr()) return; | |
103 | + // 发送请求 | |
104 | + loading.value = true; | |
105 | + const res = await getDeviceHistoryInfo(searchParams); | |
106 | + // 判断数据对象是否为空 | |
107 | + if (!Object.keys(res).length) { | |
108 | + isNull.value = false; | |
109 | + return; | |
110 | + } else { | |
111 | + isNull.value = true; | |
112 | + } | |
113 | + setChartOptions(res, value.keys); | |
114 | + loading.value = false; | |
115 | + }, | |
116 | + }); | |
117 | + | |
118 | + const getDeviceDataKey = async () => { | |
119 | + const { tbDeviceId } = props.deviceDetail || {}; | |
120 | + try { | |
121 | + deviceAttrs.value = (await getDeviceDataKeys(tbDeviceId)) || []; | |
122 | + } catch (error) {} | |
123 | + }; | |
124 | + | |
125 | + const openHistoryPanel = async () => { | |
126 | + await nextTick(); | |
127 | + method.updateSchema({ | |
128 | + field: 'keys', | |
129 | + componentProps: { | |
130 | + options: unref(deviceAttrs).map((item) => ({ label: item, value: item })), | |
131 | + }, | |
132 | + }); | |
133 | + | |
134 | + method.setFieldsValue({ | |
135 | + [SchemaFiled.START_TS]: 1 * 24 * 60 * 60 * 1000, | |
136 | + [SchemaFiled.LIMIT]: 7, | |
137 | + [SchemaFiled.AGG]: AggregateDataEnum.NONE, | |
138 | + }); | |
139 | + | |
140 | + if (!hasDeviceAttr()) return; | |
141 | + | |
142 | + const res = await getDeviceHistoryInfo({ | |
143 | + entityId: props.deviceDetail.tbDeviceId, | |
144 | + keys: unref(deviceAttrs).join(), | |
145 | + startTs: Date.now() - 1 * 24 * 60 * 60 * 1000, | |
146 | + endTs: Date.now(), | |
147 | + agg: AggregateDataEnum.NONE, | |
148 | + }); | |
149 | + | |
150 | + // 判断对象是否为空 | |
151 | + if (!Object.keys(res).length) { | |
152 | + isNull.value = false; | |
153 | + return; | |
154 | + } else { | |
155 | + isNull.value = true; | |
156 | + } | |
157 | + setChartOptions(res); | |
158 | + }; | |
159 | + | |
160 | + onMounted(async () => { | |
161 | + await getDeviceDataKey(); | |
162 | + await openHistoryPanel(); | |
163 | + }); | |
164 | + | |
165 | + onUnmounted(() => { | |
166 | + getInstance()?.clear(); | |
167 | + }); | |
168 | +</script> | |
169 | + | |
170 | +<template> | |
171 | + <section class="flex flex-col p-4 h-full" style="color: #f0f2f5"> | |
172 | + <section class="bg-white p-3"> | |
173 | + <TimePeriodForm @register="register" /> | |
174 | + </section> | |
175 | + <section class="bg-white p-3"> | |
176 | + <div v-show="isNull" ref="chartRef" :style="{ height: '550px', width: '100%' }"> | |
177 | + <Loading :loading="loading" :absolute="true" /> | |
178 | + </div> | |
179 | + <Empty v-show="!isNull" /> | |
180 | + </section> | |
181 | + </section> | |
182 | +</template> | |
183 | + | |
184 | +<style scoped></style> | ... | ... |
... | ... | @@ -473,16 +473,9 @@ export const selectDeviceAttrSchema: FormSchema[] = [ |
473 | 473 | field: 'keys', |
474 | 474 | label: '设备属性', |
475 | 475 | component: 'Select', |
476 | - // componentProps: { | |
477 | - // api: async ({ entityId }: { entityId: string }) => { | |
478 | - // if (!entityId) return []; | |
479 | - // try { | |
480 | - // const res = await getDeviceDataKeys(entityId); | |
481 | - // return res.map((item) => ({ label: item, value: item })); | |
482 | - // } catch (error) {} | |
483 | - // }, | |
484 | - // immediate: true, | |
485 | - // }, | |
476 | + componentProps: { | |
477 | + getPopupContainer: () => document.body, | |
478 | + }, | |
486 | 479 | }, |
487 | 480 | ]; |
488 | 481 | ... | ... |
1 | 1 | import { Moment } from 'moment'; |
2 | 2 | import { getPacketIntervalByRange, getPacketIntervalByValue, intervalOption } from './helper'; |
3 | 3 | import { FormSchema } from '/@/components/Form'; |
4 | +import { ColEx } from '/@/components/Form/src/types'; | |
5 | +import { useGridLayout } from '/@/hooks/component/useGridLayout'; | |
4 | 6 | export enum QueryWay { |
5 | 7 | LATEST = 'latest', |
6 | 8 | TIME_PERIOD = 'timePeriod', |
... | ... | @@ -49,6 +51,7 @@ export const defaultSchemas: FormSchema[] = [ |
49 | 51 | }) |
50 | 52 | : setFieldsValue({ [SchemaFiled.START_TS]: null }); |
51 | 53 | }, |
54 | + getPopupContainer: () => document.body, | |
52 | 55 | }; |
53 | 56 | }, |
54 | 57 | }, |
... | ... | @@ -66,6 +69,7 @@ export const defaultSchemas: FormSchema[] = [ |
66 | 69 | onChange() { |
67 | 70 | setFieldsValue({ [SchemaFiled.INTERVAL]: null }); |
68 | 71 | }, |
72 | + getPopupContainer: () => document.body, | |
69 | 73 | }; |
70 | 74 | }, |
71 | 75 | rules: [{ required: true, message: '最后数据为必选项', type: 'number' }], |
... | ... | @@ -97,11 +101,10 @@ export const defaultSchemas: FormSchema[] = [ |
97 | 101 | dates = []; |
98 | 102 | setFieldsValue({ [SchemaFiled.INTERVAL]: null }); |
99 | 103 | }, |
104 | + getPopupContainer: () => document.body, | |
100 | 105 | }; |
101 | 106 | }, |
102 | - colProps: { | |
103 | - span: 10, | |
104 | - }, | |
107 | + colProps: useGridLayout(2, 2, 2, 2, 2, 2) as unknown as ColEx, | |
105 | 108 | }, |
106 | 109 | { |
107 | 110 | field: SchemaFiled.AGG, |
... | ... | @@ -137,6 +140,7 @@ export const defaultSchemas: FormSchema[] = [ |
137 | 140 | } |
138 | 141 | return { |
139 | 142 | options, |
143 | + getPopupContainer: () => document.body, | |
140 | 144 | }; |
141 | 145 | }, |
142 | 146 | }, |
... | ... | @@ -152,6 +156,7 @@ export const defaultSchemas: FormSchema[] = [ |
152 | 156 | return { |
153 | 157 | max: 50000, |
154 | 158 | min: 7, |
159 | + getPopupContainer: () => document.body, | |
155 | 160 | }; |
156 | 161 | }, |
157 | 162 | }, | ... | ... |