Commit 419183ab5158722c42d67932ad0c658fd1b81a04
Merge branch 'ww' into 'main'
feat: device list page drawer add history data panel See merge request huang/yun-teng-iot-front!281
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,7 +46,8 @@ export const AccountDetail: AppRouteRecordRaw = { | ||
46 | { | 46 | { |
47 | path: 'account_detail/:id', | 47 | path: 'account_detail/:id', |
48 | name: 'Account_Detail', | 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 | meta: { | 51 | meta: { |
51 | title: '用户详情', | 52 | title: '用户详情', |
52 | }, | 53 | }, |
@@ -15,6 +15,9 @@ | @@ -15,6 +15,9 @@ | ||
15 | <TabPane key="2" tab="实时数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> | 15 | <TabPane key="2" tab="实时数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> |
16 | <RealTimeData :deviceDetail="deviceDetail" /> | 16 | <RealTimeData :deviceDetail="deviceDetail" /> |
17 | </TabPane> | 17 | </TabPane> |
18 | + <TabPane key="7" tab="历史数据" v-if="deviceDetail?.deviceType !== 'GATEWAY'"> | ||
19 | + <HistoryData :deviceDetail="deviceDetail" /> | ||
20 | + </TabPane> | ||
18 | <TabPane key="5" tab="命令下发" v-if="deviceDetail?.deviceType !== 'SENSOR'"> | 21 | <TabPane key="5" tab="命令下发" v-if="deviceDetail?.deviceType !== 'SENSOR'"> |
19 | <CommandIssuance :deviceDetail="deviceDetail" /> | 22 | <CommandIssuance :deviceDetail="deviceDetail" /> |
20 | </TabPane> | 23 | </TabPane> |
@@ -46,6 +49,7 @@ | @@ -46,6 +49,7 @@ | ||
46 | import TBoxDetail from '../tabs/TBoxDetail.vue'; | 49 | import TBoxDetail from '../tabs/TBoxDetail.vue'; |
47 | import CommandIssuance from '../tabs/CommandIssuance.vue'; | 50 | import CommandIssuance from '../tabs/CommandIssuance.vue'; |
48 | import { getDeviceDetail } from '/@/api/device/deviceManager'; | 51 | import { getDeviceDetail } from '/@/api/device/deviceManager'; |
52 | + import HistoryData from '../tabs/HistoryData.vue'; | ||
49 | export default defineComponent({ | 53 | export default defineComponent({ |
50 | name: 'DeviceModal', | 54 | name: 'DeviceModal', |
51 | components: { | 55 | components: { |
@@ -58,6 +62,7 @@ | @@ -58,6 +62,7 @@ | ||
58 | ChildDevice, | 62 | ChildDevice, |
59 | CommandIssuance, | 63 | CommandIssuance, |
60 | TBoxDetail, | 64 | TBoxDetail, |
65 | + HistoryData, | ||
61 | }, | 66 | }, |
62 | emits: ['reload', 'register'], | 67 | emits: ['reload', 'register'], |
63 | setup() { | 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,16 +473,9 @@ export const selectDeviceAttrSchema: FormSchema[] = [ | ||
473 | field: 'keys', | 473 | field: 'keys', |
474 | label: '设备属性', | 474 | label: '设备属性', |
475 | component: 'Select', | 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 | import { Moment } from 'moment'; | 1 | import { Moment } from 'moment'; |
2 | import { getPacketIntervalByRange, getPacketIntervalByValue, intervalOption } from './helper'; | 2 | import { getPacketIntervalByRange, getPacketIntervalByValue, intervalOption } from './helper'; |
3 | import { FormSchema } from '/@/components/Form'; | 3 | import { FormSchema } from '/@/components/Form'; |
4 | +import { ColEx } from '/@/components/Form/src/types'; | ||
5 | +import { useGridLayout } from '/@/hooks/component/useGridLayout'; | ||
4 | export enum QueryWay { | 6 | export enum QueryWay { |
5 | LATEST = 'latest', | 7 | LATEST = 'latest', |
6 | TIME_PERIOD = 'timePeriod', | 8 | TIME_PERIOD = 'timePeriod', |
@@ -49,6 +51,7 @@ export const defaultSchemas: FormSchema[] = [ | @@ -49,6 +51,7 @@ export const defaultSchemas: FormSchema[] = [ | ||
49 | }) | 51 | }) |
50 | : setFieldsValue({ [SchemaFiled.START_TS]: null }); | 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,6 +69,7 @@ export const defaultSchemas: FormSchema[] = [ | ||
66 | onChange() { | 69 | onChange() { |
67 | setFieldsValue({ [SchemaFiled.INTERVAL]: null }); | 70 | setFieldsValue({ [SchemaFiled.INTERVAL]: null }); |
68 | }, | 71 | }, |
72 | + getPopupContainer: () => document.body, | ||
69 | }; | 73 | }; |
70 | }, | 74 | }, |
71 | rules: [{ required: true, message: '最后数据为必选项', type: 'number' }], | 75 | rules: [{ required: true, message: '最后数据为必选项', type: 'number' }], |
@@ -97,11 +101,10 @@ export const defaultSchemas: FormSchema[] = [ | @@ -97,11 +101,10 @@ export const defaultSchemas: FormSchema[] = [ | ||
97 | dates = []; | 101 | dates = []; |
98 | setFieldsValue({ [SchemaFiled.INTERVAL]: null }); | 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 | field: SchemaFiled.AGG, | 110 | field: SchemaFiled.AGG, |
@@ -137,6 +140,7 @@ export const defaultSchemas: FormSchema[] = [ | @@ -137,6 +140,7 @@ export const defaultSchemas: FormSchema[] = [ | ||
137 | } | 140 | } |
138 | return { | 141 | return { |
139 | options, | 142 | options, |
143 | + getPopupContainer: () => document.body, | ||
140 | }; | 144 | }; |
141 | }, | 145 | }, |
142 | }, | 146 | }, |
@@ -152,6 +156,7 @@ export const defaultSchemas: FormSchema[] = [ | @@ -152,6 +156,7 @@ export const defaultSchemas: FormSchema[] = [ | ||
152 | return { | 156 | return { |
153 | max: 50000, | 157 | max: 50000, |
154 | min: 7, | 158 | min: 7, |
159 | + getPopupContainer: () => document.body, | ||
155 | }; | 160 | }; |
156 | }, | 161 | }, |
157 | }, | 162 | }, |