Commit 39ed06d6535485f9e77e8dcd85bd9983590bd4f0

Authored by ww
1 parent 7b757491

feat: device list page drawer add history data panel

... ... @@ -4,5 +4,8 @@
4 4 "src/locales/lang",
5 5 "public/resource/tinymce/langs"
6 6 ],
7   - "commentTranslate.targetLanguage": "en"
  7 + "commentTranslate.targetLanguage": "en",
  8 + "cSpell.words": [
  9 + "unref"
  10 + ]
8 11 }
... ...
  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 },
... ...