Commit ec6d323d158bcdc79dd035ad15baf162a17e47ae

Authored by xp.Huang
2 parents 7fd02e45 06064c91

Merge branch 'ww' into 'main'

fix: DEFECT-963 create data component fetch api happend error

See merge request huang/yun-teng-iot-front!424
... ... @@ -44,6 +44,7 @@ enum DeviceUrl {
44 44 GET_SLAVE_DEVICE = '/device/list/slave',
45 45 GET_DEVICE_ATTRIBUTE = '/device/attributes',
46 46 GET_DEVICE = '/device/list',
  47 + GET_DEVICE_RELATION = '/device/device/relation',
47 48 }
48 49
49 50 /**
... ... @@ -224,3 +225,10 @@ export const sendCommandOneway = (params: SendCommandParams) => {
224 225 { joinPrefix: false }
225 226 );
226 227 };
  228 +
  229 +export const getDeviceRelation = (params: { deviceId: string; isSlave: boolean }) => {
  230 + return defHttp.get<string>({
  231 + url: DeviceUrl.GET_DEVICE_RELATION,
  232 + params,
  233 + });
  234 +};
... ...
1 1 import { RadioRecord } from '../../../views/visual/board/detail/config/util';
  2 +import { DeviceTypeEnum } from '../../device/model/deviceModel';
2 3
3 4 export interface AddDataBoardParams {
4 5 name: string;
... ... @@ -79,12 +80,14 @@ export interface DataSource {
79 80 componentInfo: ComponentInfo;
80 81 deviceName: string;
81 82 deviceProfileId: string;
  83 + tbDeviceId: string;
82 84
83 85 // front usage
84 86 uuid?: string;
85 87 width?: number;
86 88 height?: number;
87 89 radio?: RadioRecord;
  90 + deviceType?: DeviceTypeEnum;
88 91 }
89 92
90 93 export interface DataComponentRecord {
... ...
... ... @@ -163,7 +163,7 @@
163 163 <template>
164 164 <PageWrapper dense contentFullHeight contentClass="flex">
165 165 <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
166   - <section class="flex-auto p-4 configuration-list">
  166 + <section class="flex-auto p-4 w-full configuration-list">
167 167 <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">
168 168 <BasicForm @register="registerForm" />
169 169 </div>
... ... @@ -248,7 +248,10 @@
248 248 <EllipsisOutlined key="ellipsis" />
249 249 </Dropdown>
250 250 </template>
251   - <Card.Meta :title="item.name">
  251 + <Card.Meta>
  252 + <template #title>
  253 + <span class="truncate">{{ item.name }}</span>
  254 + </template>
252 255 <template #description>
253 256 <div class="truncate h-11">
254 257 <div class="truncate">{{ item.organizationDTO.name }}</div>
... ...
... ... @@ -13,12 +13,6 @@
13 13 <Tabs.TabPane :key="FunctionType.SERVICE" tab="服务" />
14 14 <!-- <Tabs.TabPane :key="FunctionType.EVENTS" tab="事件" /> -->
15 15 <template #tabBarExtraContent>
16   - <Button @click="handlePremitter">
17   - <template #icon>
18   - <SortAscendingOutlined />
19   - </template>
20   - 格式化
21   - </Button>
22 16 <Button class="ml-2" @click="handleCopy">
23 17 <template #icon>
24 18 <CopyOutlined />
... ... @@ -30,24 +24,21 @@
30 24 </div>
31 25 <div class="relative">
32 26 <Spin :spinning="loading">
33   - <div id="jsoneditor" ref="jsoneditorEl"></div>
34   - <div class="absolute top-0 left-0 w-full h-full"></div>
  27 + <Textarea :disabled="true" :auto-size="{ minRows: 20, maxRows: 20 }" :value="jsonValue" />
35 28 </Spin>
36 29 </div>
37 30 </div>
38 31 </template>
39 32 <script lang="ts" setup>
40   - import { ref, unref, onMounted, nextTick } from 'vue';
41   - import { CopyOutlined, SortAscendingOutlined } from '@ant-design/icons-vue';
42   - import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
  33 + import { ref, unref, onMounted } from 'vue';
  34 + import { CopyOutlined } from '@ant-design/icons-vue';
43 35 import { useMessage } from '/@/hooks/web/useMessage';
44   - import jsoneditor from 'jsoneditor';
45   - import 'jsoneditor/dist/jsoneditor.min.css';
46   - import { Button, Typography, TypographyParagraph, Tabs, Spin } from 'ant-design-vue';
  36 + import { Button, Typography, TypographyParagraph, Tabs, Spin, Textarea } from 'ant-design-vue';
47 37 import { FunctionType } from './config';
48 38 import useCommon from '../hook/useCommon';
49 39 import { DeviceRecord } from '/@/api/device/model/deviceModel';
50 40 import { getModelTsl } from '/@/api/device/modelOfMatter';
  41 + import { useClipboard } from '@vueuse/core';
51 42
52 43 const props = defineProps<{
53 44 record: DeviceRecord;
... ... @@ -61,40 +52,17 @@
61 52
62 53 const jsonValue = ref();
63 54
64   - const jsonInstance = ref<{
65   - set: (value?: Recordable) => void;
66   - get: () => Recordable;
67   - }>();
68   -
69   - const jsoneditorEl = ref<HTMLDivElement>();
70   -
71 55 const activeKey = ref(FunctionType.PROPERTIES);
72 56
73   - onMounted(() => {
74   - nextTick(() => {
75   - let options = {
76   - mode: 'code',
77   - mainMenuBar: false,
78   - statusBar: false,
79   - };
80   - let editor = new jsoneditor(jsoneditorEl.value, options);
81   - editor.set(jsonValue.value);
82   - jsonInstance.value = editor;
83   - });
84   - });
85   -
86   - const { clipboardRef, copiedRef } = useCopyToClipboard();
87   -
  57 + const { copy, isSupported } = useClipboard({ source: jsonValue });
88 58 const handleCopy = () => {
89 59 try {
90   - const valueRef = unref(jsonInstance)?.get();
91   - const value = JSON.stringify(unref(valueRef));
92   - if (!value) {
  60 + if (!jsonValue.value) {
93 61 createMessage.warning('请输入要拷贝的内容!');
94 62 return;
95 63 }
96   - clipboardRef.value = value;
97   - if (unref(copiedRef)) {
  64 + if (unref(isSupported)) {
  65 + copy();
98 66 createMessage.success('复制成功!');
99 67 }
100 68 } catch (e) {
... ... @@ -103,25 +71,18 @@
103 71 };
104 72
105 73 const getFormData = () => {
106   - const value = unref(jsonInstance)?.get();
107   - if (!value) return;
108   - return value;
  74 + return JSON.parse(jsonValue.value);
109 75 };
110 76
111 77 const resetFormData = () => {
112   - unref(jsonInstance)?.set();
113   - };
114   -
115   - const handlePremitter = () => {
116   - const value = unref(jsonInstance)?.get();
117   - return unref(jsonInstance)?.set(value);
  78 + jsonValue.value = null;
118 79 };
119 80
120 81 const handleSwitchTsl = async (functionType: FunctionType) => {
121 82 try {
122 83 loading.value = true;
123 84 const record = await getModelTsl({ deviceProfileId: props.record.id, functionType });
124   - jsonInstance.value?.set(record);
  85 + jsonValue.value = JSON.stringify(record, null, 2);
125 86 } catch (error) {
126 87 } finally {
127 88 loading.value = false;
... ... @@ -138,10 +99,3 @@
138 99 resetFormData,
139 100 });
140 101 </script>
141   -
142   -<style lang="less" scope>
143   - #jsoneditor {
144   - width: 100%;
145   - height: 450px;
146   - }
147   -</style>
... ...
... ... @@ -41,9 +41,9 @@
41 41 >
42 42 <label class="switch">
43 43 <input
44   - :value="props.value?.value"
  44 + :value="!!props.value?.value"
45 45 type="checkbox"
46   - :checked="props.value?.value"
  46 + :checked="!!props.value?.value"
47 47 @change="handleChange"
48 48 />
49 49 <div class="button">
... ...
1 1 import { DataComponentRecord, DataSource } from '/@/api/dataBoard/model';
  2 +import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
2 3
3 4 export interface ControlComponentLayout {
4 5 [key: string]: any;
... ... @@ -14,6 +15,8 @@ export interface ControlComponentValue {
14 15 fontColor?: string;
15 16 slaveDeviceId?: string;
16 17 deviceProfileId?: string;
  18 + deviceType?: DeviceTypeEnum;
  19 + organizationId?: string;
17 20 }
18 21
19 22 export const ControlComponentDefaultConfig: ControlComponentValue = {
... ... @@ -34,7 +37,9 @@ export const transformControlConfig = (
34 37 attributeRename: dataSourceRecord.attributeRename,
35 38 deviceProfileId: dataSourceRecord.deviceProfileId,
36 39 deviceId: dataSourceRecord.deviceId,
  40 + deviceType: dataSourceRecord.deviceType,
37 41 slaveDeviceId: dataSourceRecord.slaveDeviceId,
  42 + organizationId: dataSourceRecord.organizationId,
38 43 } as ControlComponentValue,
39 44 };
40 45 };
... ...
1 1 import { ControlComponentValue } from './control.config';
2 2 import { getDeviceProfile } from '/@/api/alarm/position';
3   -import { sendCommandOneway } from '/@/api/dataBoard';
  3 +import { getDeviceRelation, sendCommandOneway } from '/@/api/dataBoard';
  4 +import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
4 5 import { getModelServices } from '/@/api/device/modelOfMatter';
5 6 import { useMessage } from '/@/hooks/web/useMessage';
6 7 import { isString } from '/@/utils/is';
... ... @@ -8,7 +9,9 @@ import { isString } from '/@/utils/is';
8 9 const { createMessage } = useMessage();
9 10 export function useSendCommand() {
10 11 const sendCommand = async (record: ControlComponentValue, value: any) => {
11   - const { deviceId, deviceProfileId, attribute } = record;
  12 + if (!record) return;
  13 + const { deviceProfileId, attribute, deviceType } = record;
  14 + let { deviceId } = record;
12 15 if (!deviceId) return;
13 16 try {
14 17 const list = await getDeviceProfile();
... ... @@ -23,6 +26,12 @@ export function useSendCommand() {
23 26 const sendCommand = record?.functionJson.inputData?.at(0)?.serviceCommand || '';
24 27 params = isString(sendCommand) ? sendCommand : JSON.stringify(sendCommand);
25 28 }
  29 + if (deviceType === DeviceTypeEnum.SENSOR) {
  30 + deviceId = await getDeviceRelation({
  31 + deviceId,
  32 + isSlave: deviceType === DeviceTypeEnum.SENSOR,
  33 + });
  34 + }
26 35 await sendCommandOneway({
27 36 deviceId,
28 37 value: {
... ...
... ... @@ -27,15 +27,19 @@
27 27 const { dataSource = [] } = params;
28 28 const deviceRecord = dataSource?.at(0) || ({} as DataSource);
29 29 if (!deviceRecord.organizationId) return;
30   - const deviceList = await getAllDeviceByOrg(deviceRecord.organizationId);
  30 + const deviceList = await getAllDeviceByOrg(
  31 + deviceRecord.organizationId,
  32 + deviceRecord.deviceProfileId
  33 + );
31 34 const options = deviceList
32   - .filter((item) => item.id === deviceRecord.deviceId)
33   - .map((item) => ({ ...item, label: item.name, value: item.id }));
  35 + .filter((item) => item.tbDeviceId === deviceRecord.deviceId)
  36 + .map((item) => ({ ...item, label: item.name, value: item.tbDeviceId }));
34 37 const attKey = dataSource.map((item) => ({
35 38 ...item,
36 39 label: item.attribute,
37 40 value: item.attribute,
38 41 }));
  42 + console.log(options);
39 43 updateSchema([
40 44 {
41 45 field: SchemaFiled.DEVICE_ID,
... ...
... ... @@ -22,6 +22,7 @@
22 22 import { formatToDateTime } from '/@/utils/dateUtil';
23 23 import { isEqual } from 'lodash-es';
24 24 import { useAsyncQueue } from '/@/views/device/localtion/useAsyncQueue';
  25 + import { useMessage } from '/@/hooks/web/useMessage';
25 26
26 27 // useVisualBoardContext();
27 28 type TrackRecord = Record<'lng' | 'lat' | 'ts', number>;
... ... @@ -199,7 +200,13 @@
199 200 return (trackAni.value || {})._status;
200 201 });
201 202
  203 + const { createMessage } = useMessage();
202 204 const handlePlay = () => {
  205 + const { start, end } = timeRange;
  206 +
  207 + if (!props.random && (!start || !end)) {
  208 + createMessage.warning('请先选择时间范围');
  209 + }
203 210 if (unref(getTrackPlayStatus) === TrackAnimationStatus.DONE) unref(trackAni).start();
204 211 else if (unref(getTrackPlayStatus) === TrackAnimationStatus.PLAY) unref(trackAni).pause();
205 212 else if (unref(getTrackPlayStatus) === TrackAnimationStatus.PAUSE) unref(trackAni).continue();
... ...
... ... @@ -289,7 +289,7 @@
289 289 <Alert type="info" show-icon v-if="isMapComponent">
290 290 <template #description>
291 291 <div>
292   - 地图组件,需绑定两个数据源,且数据源为同一设备。第一数据源为经度,第二数据源为度,否则地图组件不能正常显示。
  292 + 地图组件,需绑定两个数据源,且数据源为同一设备。第一数据源为经度,第二数据源为度,否则地图组件不能正常显示。
293 293 </div>
294 294 </template>
295 295 </Alert>
... ...
1 1 <script lang="ts" setup>
2 2 import { nextTick, Ref, ref, unref } from 'vue';
3 3 import { getDeviceHistoryInfo } from '/@/api/alarm/position';
4   - import { Empty } from 'ant-design-vue';
  4 + import { Empty, Tooltip, Button } from 'ant-design-vue';
5 5 import { useECharts } from '/@/hooks/web/useECharts';
6 6 import { AggregateDataEnum } from '/@/views/device/localtion/config.data';
7 7 import { useGridLayout } from '/@/hooks/component/useGridLayout';
... ... @@ -14,17 +14,29 @@
14 14 import { useModalInner } from '/@/components/Modal';
15 15 import { getAllDeviceByOrg } from '/@/api/dataBoard';
16 16 import { useHistoryData } from '/@/views/device/list/hook/useHistoryData';
  17 + import { BasicTable, useTable } from '/@/components/Table';
  18 + import { LineChartOutlined, BarsOutlined } from '@ant-design/icons-vue';
  19 + import { formatToDateTime } from '/@/utils/dateUtil';
17 20
18 21 type DeviceOption = Record<'label' | 'value' | 'organizationId', string>;
19 22
20 23 defineEmits(['register']);
21 24
  25 + enum Mode {
  26 + TABLE = 'table',
  27 + CHART = 'chart',
  28 + }
  29 +
  30 + const mode = ref<Mode>(Mode.CHART);
  31 +
22 32 const chartRef = ref();
23 33
24 34 const loading = ref(false);
25 35
26 36 const isNull = ref(false);
27 37
  38 + const historyData = ref<{ ts: number; value: string; name: string }[]>([]);
  39 +
28 40 const { deviceAttrs, getDeviceKeys, getSearchParams, setChartOptions, getDeviceAttribute } =
29 41 useHistoryData();
30 42
... ... @@ -52,30 +64,74 @@
52 64 loading: loading as unknown as boolean,
53 65 },
54 66 async submitFunc() {
55   - // 表单验证
56   - await method.validate();
57   - const value = method.getFieldsValue();
58   - const searchParams = getSearchParams(value);
59   - if (!hasDeviceAttr()) return;
60   - // 发送请求
61   - loading.value = true;
62   - const res = await getDeviceHistoryInfo(searchParams);
63   - loading.value = false;
64   - // 判断数据对象是否为空
65   - if (!Object.keys(res).length) {
66   - isNull.value = false;
67   - return;
68   - } else {
69   - isNull.value = true;
70   - }
71   -
72   - const selectedKeys = unref(deviceAttrs).find(
73   - (item) => item.identifier === value[SchemaFiled.KEYS]
74   - );
75   - setOptions(setChartOptions(res, selectedKeys));
  67 + search();
76 68 },
77 69 });
78 70
  71 + const search = async () => {
  72 + // 表单验证
  73 + await method.validate();
  74 + const value = method.getFieldsValue();
  75 + const searchParams = getSearchParams(value);
  76 + if (!hasDeviceAttr()) return;
  77 + // 发送请求
  78 + loading.value = true;
  79 + const res = await getDeviceHistoryInfo(searchParams);
  80 + historyData.value = getTableHistoryData(res);
  81 + loading.value = false;
  82 + // 判断数据对象是否为空
  83 + if (!Object.keys(res).length) {
  84 + isNull.value = false;
  85 + return;
  86 + } else {
  87 + isNull.value = true;
  88 + }
  89 +
  90 + const selectedKeys = unref(deviceAttrs).find(
  91 + (item) => item.identifier === value[SchemaFiled.KEYS]
  92 + );
  93 + setOptions(setChartOptions(res, selectedKeys));
  94 + };
  95 +
  96 + const getTableHistoryData = (record: Recordable<{ ts: number; value: string }[]>) => {
  97 + const keys = Object.keys(record);
  98 + const list = keys.reduce((prev, next) => {
  99 + const list = record[next].map((item) => {
  100 + return {
  101 + ...item,
  102 + name: next,
  103 + };
  104 + });
  105 + return [...prev, ...list];
  106 + }, []);
  107 + return list;
  108 + };
  109 +
  110 + const [registerTable] = useTable({
  111 + showIndexColumn: false,
  112 + showTableSetting: false,
  113 + dataSource: historyData,
  114 + maxHeight: 300,
  115 + size: 'small',
  116 + columns: [
  117 + {
  118 + title: '属性',
  119 + dataIndex: 'name',
  120 + },
  121 + {
  122 + title: '值',
  123 + dataIndex: 'value',
  124 + },
  125 + {
  126 + title: '更新时间',
  127 + dataIndex: 'ts',
  128 + format: (val) => {
  129 + return formatToDateTime(val, 'YYYY-MM-DD HH:mm:ss');
  130 + },
  131 + },
  132 + ],
  133 + });
  134 +
79 135 const getDeviceDataKey = async (record: DeviceOption) => {
80 136 const { organizationId, value } = record;
81 137 try {
... ... @@ -115,9 +171,9 @@
115 171 agg: AggregateDataEnum.NONE,
116 172 limit: 7,
117 173 });
118   -
  174 + historyData.value = getTableHistoryData(res);
119 175 // 判断对象是否为空
120   - if (!Object.keys(res).length) {
  176 + if (!Object.keys(unref(historyData)).length) {
121 177 isNull.value = false;
122 178 return;
123 179 } else {
... ... @@ -181,6 +237,10 @@
181 237 const handleCancel = () => {
182 238 destory();
183 239 };
  240 +
  241 + const switchMode = (flag: Mode) => {
  242 + mode.value = flag;
  243 + };
184 244 </script>
185 245
186 246 <template>
... ... @@ -201,14 +261,65 @@
201 261 <BasicForm @register="register" />
202 262 </section>
203 263 <section class="bg-white p-3" style="min-height: 350px">
204   - <div v-show="isNull" ref="chartRef" :style="{ height: '350px', width: '100%' }">
  264 + <div v-show="mode === Mode.CHART" class="flex h-70px items-center justify-end p-2">
  265 + <Tooltip title="图表模式">
  266 + <Button
  267 + :class="[mode === Mode.CHART && '!bg-blue-500 svg:text-light-50']"
  268 + class="!p-2 !children:flex flex justify-center items-center border-r-0"
  269 + @click="switchMode(Mode.CHART)"
  270 + >
  271 + <LineChartOutlined />
  272 + </Button>
  273 + </Tooltip>
  274 +
  275 + <Tooltip title="列表模式">
  276 + <Button
  277 + class="!p-2 !children:flex flex justify-center items-center"
  278 + @click="switchMode(Mode.TABLE)"
  279 + >
  280 + <BarsOutlined />
  281 + </Button>
  282 + </Tooltip>
  283 + </div>
  284 +
  285 + <div
  286 + v-show="isNull && mode === Mode.CHART"
  287 + ref="chartRef"
  288 + :style="{ height: '350px', width: '100%' }"
  289 + >
205 290 <Loading :loading="loading" :absolute="true" />
206 291 </div>
207 292 <Empty
  293 + v-if="mode === Mode.CHART"
208 294 class="h-350px flex flex-col justify-center items-center"
209 295 description="暂无数据,请选择设备查询"
210 296 v-show="!isNull"
211 297 />
  298 +
  299 + <BasicTable v-show="mode === Mode.TABLE" @register="registerTable">
  300 + <template #toolbar>
  301 + <div v-show="mode === Mode.TABLE" class="flex h-70px items-center justify-end p-2">
  302 + <Tooltip title="图表模式">
  303 + <Button
  304 + class="!p-2 !children:flex flex justify-center items-center border-r-0"
  305 + @click="switchMode(Mode.CHART)"
  306 + >
  307 + <LineChartOutlined />
  308 + </Button>
  309 + </Tooltip>
  310 +
  311 + <Tooltip title="列表模式">
  312 + <Button
  313 + :class="[mode === Mode.TABLE && '!bg-blue-500 svg:text-light-50']"
  314 + class="!p-2 !children:flex flex justify-center items-center"
  315 + @click="switchMode(Mode.TABLE)"
  316 + >
  317 + <BarsOutlined />
  318 + </Button>
  319 + </Tooltip>
  320 + </div>
  321 + </template>
  322 + </BasicTable>
212 323 </section>
213 324 </section>
214 325 </BasicModal>
... ...
... ... @@ -143,7 +143,7 @@
143 143 };
144 144
145 145 const handleOpenDetailModal = () => {
146   - openModal();
  146 + openModal(true, { isEdit: false });
147 147 };
148 148
149 149 const handleRemove = async (record: DataBoardRecord) => {
... ...