Commit e535013c053a94c575f4ac130e0709b68c8d8cca

Authored by ww
1 parent 4ba5ef53

feat: widget dataSource add slaveDeviceId && deviceName

@@ -5,6 +5,7 @@ import { @@ -5,6 +5,7 @@ import {
5 DataComponentRecord, 5 DataComponentRecord,
6 GetDataBoardParams, 6 GetDataBoardParams,
7 Layout, 7 Layout,
  8 + MasterDeviceList,
8 UpdateDataBoardLayoutParams, 9 UpdateDataBoardLayoutParams,
9 UpdateDataBoardParams, 10 UpdateDataBoardParams,
10 UpdateDataComponentParams, 11 UpdateDataComponentParams,
@@ -27,7 +28,9 @@ enum DataComponentUrl { @@ -27,7 +28,9 @@ enum DataComponentUrl {
27 } 28 }
28 29
29 enum DeviceUrl { 30 enum DeviceUrl {
30 - GET_DEVICE = '/device/list', 31 + GET_DEVICE = '/device/list/master',
  32 + GET_SLAVE_DEVICE = '/device/list/slave',
  33 + GET_DEVICE_ATTRIBUTE = '/plugins/telemetry',
31 } 34 }
32 35
33 /** 36 /**
@@ -138,13 +141,42 @@ export const updateDataComponent = (params: UpdateDataComponentParams) => { @@ -138,13 +141,42 @@ export const updateDataComponent = (params: UpdateDataComponentParams) => {
138 }; 141 };
139 142
140 /** 143 /**
141 - * @description 获取所有设备 144 + * @description 获取所有设备
142 * @param params 145 * @param params
143 * @returns 146 * @returns
144 */ 147 */
145 -export const getAllAvailableDeviceByOrgId = (params: string) => {  
146 - return defHttp.get({  
147 - url: `${DeviceUrl.GET_DEVICE}/DIRECT_CONNECTION`,  
148 - params: { organizationId: params }, 148 +export const getAllDeviceByOrg = (params: string) => {
  149 + return defHttp.get<MasterDeviceList[]>({
  150 + url: `${DeviceUrl.GET_DEVICE}/${params}`,
149 }); 151 });
150 }; 152 };
  153 +
  154 +/**
  155 + * @description 获取网关子设备
  156 + * @param params
  157 + * @returns
  158 + */
  159 +export const getGatewaySlaveDevice = (params: { organizationId: string; masterId: string }) => {
  160 + const { masterId, organizationId } = params;
  161 + return defHttp.get<MasterDeviceList[]>({
  162 + url: `${DeviceUrl.GET_SLAVE_DEVICE}/${organizationId}`,
  163 + params: { masterId },
  164 + });
  165 +};
  166 +
  167 +/**
  168 + * @description 获取设备属性
  169 + * @param params
  170 + * @returns
  171 + */
  172 +export const getDeviceAttributes = (params: { entityType?: string; deviceId: string }) => {
  173 + const { entityType = 'DEVICE', deviceId } = params;
  174 + return defHttp.get<string[]>(
  175 + {
  176 + url: `${DeviceUrl.GET_DEVICE_ATTRIBUTE}/${entityType}/${deviceId}/keys/timeseries`,
  177 + },
  178 + {
  179 + joinPrefix: false,
  180 + }
  181 + );
  182 +};
@@ -72,6 +72,8 @@ export interface DataSource { @@ -72,6 +72,8 @@ export interface DataSource {
72 organizationId: string; 72 organizationId: string;
73 attributeRename: string; 73 attributeRename: string;
74 deviceRename: string; 74 deviceRename: string;
  75 + slaveDeviceId: string;
  76 + isGatewayDevice: boolean;
75 componentInfo: ComponentInfo; 77 componentInfo: ComponentInfo;
76 } 78 }
77 79
@@ -119,3 +121,9 @@ export interface UpdateDataComponentParams { @@ -119,3 +121,9 @@ export interface UpdateDataComponentParams {
119 boardId: string; 121 boardId: string;
120 record: Partial<DataComponentRecord>; 122 record: Partial<DataComponentRecord>;
121 } 123 }
  124 +
  125 +export interface MasterDeviceList {
  126 + deviceType: 'DIRECT_CONNECTION' | 'GATEWAY';
  127 + id: string;
  128 + name: string;
  129 +}
@@ -101,6 +101,7 @@ export const instrumentComponent1 = (params?: { @@ -101,6 +101,7 @@ export const instrumentComponent1 = (params?: {
101 borderRadius: 8, 101 borderRadius: 8,
102 offsetCenter: [0, '-15%'], 102 offsetCenter: [0, '-15%'],
103 // fontSize: 16, 103 // fontSize: 16,
  104 + fontSize: 14,
104 fontWeight: 'bolder', 105 fontWeight: 'bolder',
105 formatter: `{value} ${unit ?? '°C'}`, 106 formatter: `{value} ${unit ?? '°C'}`,
106 color: params?.fontColor || 'inherit', 107 color: params?.fontColor || 'inherit',
1 import { DataBoardLayoutInfo } from '../../types/type'; 1 import { DataBoardLayoutInfo } from '../../types/type';
2 -import { DataSource } from '/@/api/dataBoard/model';  
3 -  
4 -// export interface DataSource {  
5 -// id: number | string;  
6 -// width: number;  
7 -// height: number;  
8 -  
9 -// [key: string]: any;  
10 -// }  
11 2
12 export type WidgetWrapperRegister = (dataSource: DataBoardLayoutInfo['record'][]) => any; 3 export type WidgetWrapperRegister = (dataSource: DataBoardLayoutInfo['record'][]) => any;
@@ -68,6 +68,7 @@ @@ -68,6 +68,7 @@
68 componentInfo: { ...componentInfo }, 68 componentInfo: { ...componentInfo },
69 }); 69 });
70 } 70 }
  71 + console.log(_dataSource);
71 return _dataSource; 72 return _dataSource;
72 }; 73 };
73 74
@@ -185,19 +186,19 @@ @@ -185,19 +186,19 @@
185 </div> 186 </div>
186 <div class="flex justify-center gap-3 w-24"> 187 <div class="flex justify-center gap-3 w-24">
187 <Tooltip title="复制"> 188 <Tooltip title="复制">
188 - <CopyOutlined @click="handleCopy(item)" class="cursor-pointer text-lg !leading-30px" /> 189 + <CopyOutlined @click="handleCopy(item)" class="cursor-pointer text-lg !leading-52px" />
189 </Tooltip> 190 </Tooltip>
190 <Tooltip title="设置"> 191 <Tooltip title="设置">
191 <SettingOutlined 192 <SettingOutlined
192 v-show="showSettingButton" 193 v-show="showSettingButton"
193 @click="handleSetting(item)" 194 @click="handleSetting(item)"
194 - class="cursor-pointer text-lg !leading-30px" 195 + class="cursor-pointer text-lg !leading-52px"
195 /> 196 />
196 </Tooltip> 197 </Tooltip>
197 <Tooltip title="删除"> 198 <Tooltip title="删除">
198 <DeleteOutlined 199 <DeleteOutlined
199 @click="handleDelete(item)" 200 @click="handleDelete(item)"
200 - class="cursor-pointer text-lg !leading-30px" 201 + class="cursor-pointer text-lg !leading-52px"
201 /> 202 />
202 </Tooltip> 203 </Tooltip>
203 </div> 204 </div>
@@ -5,9 +5,8 @@ @@ -5,9 +5,8 @@
5 import VisualConfiguration from './VisualConfiguration.vue'; 5 import VisualConfiguration from './VisualConfiguration.vue';
6 import { computed, ref, unref } from 'vue'; 6 import { computed, ref, unref } from 'vue';
7 import { RouteParams, useRoute } from 'vue-router'; 7 import { RouteParams, useRoute } from 'vue-router';
8 - import { addDataComponent, updateDataBoardLayout, updateDataComponent } from '/@/api/dataBoard'; 8 + import { addDataComponent, updateDataComponent } from '/@/api/dataBoard';
9 import { useModalInner } from '/@/components/Modal'; 9 import { useModalInner } from '/@/components/Modal';
10 - import { DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH } from '../../config/config';  
11 import { DataBoardLayoutInfo } from '../../types/type'; 10 import { DataBoardLayoutInfo } from '../../types/type';
12 import { useMessage } from '/@/hooks/web/useMessage'; 11 import { useMessage } from '/@/hooks/web/useMessage';
13 12
@@ -44,9 +43,11 @@ @@ -44,9 +43,11 @@
44 }>(); 43 }>();
45 44
46 const handleSubmit = () => { 45 const handleSubmit = () => {
  46 + console.log({ isEdit: unref(isEdit) });
47 const { getAllDataSourceFieldValue } = unref(basicConfigurationEl)!; 47 const { getAllDataSourceFieldValue } = unref(basicConfigurationEl)!;
48 const value = getAllDataSourceFieldValue(); 48 const value = getAllDataSourceFieldValue();
49 unref(isEdit) ? handleUpdateComponent(value) : handleAddComponent(value); 49 unref(isEdit) ? handleUpdateComponent(value) : handleAddComponent(value);
  50 + isEdit.value = false;
50 }; 51 };
51 52
52 const handleAddComponent = async (value: Recordable) => { 53 const handleAddComponent = async (value: Recordable) => {
  1 +<script setup lang="ts"></script>
  2 +
  3 +<template>
  4 + <div>历史趋势</div>
  5 +</template>
1 -import { getAllAvailableDeviceByOrgId } from '/@/api/dataBoard';  
2 -import { getAttribute } from '/@/api/ruleengine/ruleengineApi'; 1 +import { getAllDeviceByOrg, getDeviceAttributes, getGatewaySlaveDevice } from '/@/api/dataBoard';
  2 +import { MasterDeviceList } from '/@/api/dataBoard/model';
3 import { getOrganizationList } from '/@/api/system/system'; 3 import { getOrganizationList } from '/@/api/system/system';
4 import { FormSchema } from '/@/components/Form'; 4 import { FormSchema } from '/@/components/Form';
5 import { copyTransFun } from '/@/utils/fnUtils'; 5 import { copyTransFun } from '/@/utils/fnUtils';
6 -import { to } from '/@/utils/to'; 6 +
  7 +export enum BasicConfigField {
  8 + NAME = 'name',
  9 + REMARK = 'remark',
  10 +}
  11 +
  12 +const getDeviceAttribute = async (deviceId: string) => {
  13 + try {
  14 + const data = await getDeviceAttributes({ deviceId });
  15 + if (data) return data.map((item) => ({ label: item, value: item }));
  16 + } catch (error) {}
  17 + return [];
  18 +};
7 19
8 export enum DataSourceField { 20 export enum DataSourceField {
  21 + IS_GATEWAY_DEVICE = 'isGatewayDevice',
  22 + ORIGINATION_ID = 'organizationId',
9 DEVICE_ID = 'deviceId', 23 DEVICE_ID = 'deviceId',
  24 + SLAVE_DEVICE_ID = 'slaveDeviceId',
  25 + ATTRIBUTE = 'attribute',
  26 + ATTRIBUTE_RENAME = 'attributeRename',
  27 + DEVICE_NAME = 'deviceName',
  28 + DEVICE_RENAME = 'deviceRename',
10 } 29 }
11 30
12 export const basicSchema: FormSchema[] = [ 31 export const basicSchema: FormSchema[] = [
13 { 32 {
14 - field: 'name', 33 + field: BasicConfigField.NAME,
15 label: '组件名称', 34 label: '组件名称',
16 component: 'Input', 35 component: 'Input',
17 componentProps: { 36 componentProps: {
@@ -19,7 +38,7 @@ export const basicSchema: FormSchema[] = [ @@ -19,7 +38,7 @@ export const basicSchema: FormSchema[] = [
19 }, 38 },
20 }, 39 },
21 { 40 {
22 - field: 'remark', 41 + field: BasicConfigField.REMARK,
23 label: '组件备注', 42 label: '组件备注',
24 component: 'InputTextArea', 43 component: 'InputTextArea',
25 componentProps: { 44 componentProps: {
@@ -30,10 +49,22 @@ export const basicSchema: FormSchema[] = [ @@ -30,10 +49,22 @@ export const basicSchema: FormSchema[] = [
30 49
31 export const dataSourceSchema: FormSchema[] = [ 50 export const dataSourceSchema: FormSchema[] = [
32 { 51 {
33 - field: 'organizationId', 52 + field: DataSourceField.IS_GATEWAY_DEVICE,
  53 + component: 'Switch',
  54 + label: '是否是网关设备',
  55 + show: false,
  56 + },
  57 + {
  58 + field: DataSourceField.DEVICE_NAME,
  59 + component: 'Input',
  60 + label: '设备名',
  61 + show: false,
  62 + },
  63 + {
  64 + field: DataSourceField.ORIGINATION_ID,
34 component: 'ApiTreeSelect', 65 component: 'ApiTreeSelect',
35 label: '组织', 66 label: '组织',
36 - colProps: { span: 6 }, 67 + colProps: { span: 8 },
37 componentProps({ formActionType }) { 68 componentProps({ formActionType }) {
38 const { setFieldsValue } = formActionType; 69 const { setFieldsValue } = formActionType;
39 return { 70 return {
@@ -44,32 +75,47 @@ export const dataSourceSchema: FormSchema[] = [ @@ -44,32 +75,47 @@ export const dataSourceSchema: FormSchema[] = [
44 return data; 75 return data;
45 }, 76 },
46 onChange() { 77 onChange() {
47 - setFieldsValue({ deviceId: null, attribute: null }); 78 + setFieldsValue({
  79 + [DataSourceField.DEVICE_ID]: null,
  80 + [DataSourceField.ATTRIBUTE]: null,
  81 + [DataSourceField.SLAVE_DEVICE_ID]: null,
  82 + [DataSourceField.IS_GATEWAY_DEVICE]: false,
  83 + });
48 }, 84 },
49 getPopupContainer: () => document.body, 85 getPopupContainer: () => document.body,
50 }; 86 };
51 }, 87 },
52 }, 88 },
53 { 89 {
54 - field: 'deviceId', 90 + field: DataSourceField.DEVICE_ID,
55 component: 'ApiSelect', 91 component: 'ApiSelect',
56 label: '设备', 92 label: '设备',
57 - colProps: { span: 5 }, 93 + colProps: { span: 8 },
58 componentProps({ formModel, formActionType }) { 94 componentProps({ formModel, formActionType }) {
59 const { setFieldsValue } = formActionType; 95 const { setFieldsValue } = formActionType;
60 - const orgId = formModel['organizationId']; 96 + const organizationId = formModel[DataSourceField.ORIGINATION_ID];
61 return { 97 return {
62 api: async () => { 98 api: async () => {
63 - if (orgId) {  
64 - const [, data] = await to<Record<'id' | 'name', string>[]>(  
65 - getAllAvailableDeviceByOrgId(orgId)  
66 - );  
67 - if (data) return data.map((item) => ({ label: item.name, value: item.id })); 99 + if (organizationId) {
  100 + try {
  101 + const data = await getAllDeviceByOrg(organizationId);
  102 + if (data)
  103 + return data.map((item) => ({
  104 + label: item.name,
  105 + value: item.id,
  106 + deviceType: item.deviceType,
  107 + }));
  108 + } catch (error) {}
68 } 109 }
69 return []; 110 return [];
70 }, 111 },
71 - onChange() {  
72 - setFieldsValue({ attribute: null }); 112 + onChange(_value, record: Record<'value' | 'label' | 'deviceType', string>) {
  113 + setFieldsValue({
  114 + [DataSourceField.ATTRIBUTE]: null,
  115 + [DataSourceField.IS_GATEWAY_DEVICE]: record?.deviceType === 'GATEWAY',
  116 + [DataSourceField.SLAVE_DEVICE_ID]: null,
  117 + [DataSourceField.DEVICE_NAME]: record?.label,
  118 + });
73 }, 119 },
74 placeholder: '请选择设备', 120 placeholder: '请选择设备',
75 getPopupContainer: () => document.body, 121 getPopupContainer: () => document.body,
@@ -77,19 +123,68 @@ export const dataSourceSchema: FormSchema[] = [ @@ -77,19 +123,68 @@ export const dataSourceSchema: FormSchema[] = [
77 }, 123 },
78 }, 124 },
79 { 125 {
80 - field: 'attribute', 126 + field: DataSourceField.SLAVE_DEVICE_ID,
  127 + label: '网关子设备',
  128 + component: 'ApiSelect',
  129 + colProps: { span: 8 },
  130 + ifShow({ model }) {
  131 + return model['isGatewayDevice'];
  132 + },
  133 + dynamicRules({ model }) {
  134 + return [{ required: model[DataSourceField.IS_GATEWAY_DEVICE], message: '请选择网关子设备' }];
  135 + },
  136 + componentProps({ formModel, formActionType }) {
  137 + const { setFieldsValue } = formActionType;
  138 + const organizationId = formModel[DataSourceField.ORIGINATION_ID];
  139 + const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE];
  140 + const deviceId = formModel[DataSourceField.DEVICE_ID];
  141 + return {
  142 + api: async () => {
  143 + if (organizationId && isGatewayDevice) {
  144 + try {
  145 + const data = await getGatewaySlaveDevice({ organizationId, masterId: deviceId });
  146 + if (data)
  147 + return data.map((item) => ({
  148 + label: item.name,
  149 + value: item.id,
  150 + deviceType: item.deviceType,
  151 + }));
  152 + } catch (error) {}
  153 + }
  154 + return [];
  155 + },
  156 + onChange(_value, record: Record<'value' | 'label' | 'deviceType', string>) {
  157 + setFieldsValue({
  158 + [DataSourceField.ATTRIBUTE]: null,
  159 + [DataSourceField.DEVICE_NAME]: record?.label,
  160 + });
  161 + },
  162 + placeholder: '请选择网关子设备',
  163 + getPopupContainer: () => document.body,
  164 + };
  165 + },
  166 + },
  167 + {
  168 + field: DataSourceField.ATTRIBUTE,
81 component: 'ApiSelect', 169 component: 'ApiSelect',
82 label: '属性', 170 label: '属性',
83 - colProps: { span: 5 }, 171 + colProps: { span: 8 },
84 componentProps({ formModel }) { 172 componentProps({ formModel }) {
85 - const orgId = formModel['organizationId'];  
86 - const deviceId = formModel['deviceId']; 173 + const organizationId = formModel[DataSourceField.ORIGINATION_ID];
  174 + const isGatewayDevice = formModel[DataSourceField.IS_GATEWAY_DEVICE];
  175 + const deviceId = formModel[DataSourceField.DEVICE_ID];
  176 + const slaveDeviceId = formModel[DataSourceField.SLAVE_DEVICE_ID];
87 return { 177 return {
88 api: async () => { 178 api: async () => {
89 - if (orgId && deviceId) {  
90 - const [, data] = await to<string[]>(getAttribute(orgId, deviceId));  
91 - // TODO attribute exist null  
92 - if (data) return data.filter(Boolean).map((item) => ({ label: item, value: item })); 179 + if (organizationId && deviceId) {
  180 + try {
  181 + if (isGatewayDevice && slaveDeviceId) {
  182 + return await getDeviceAttribute(slaveDeviceId);
  183 + }
  184 + if (!isGatewayDevice) {
  185 + return await getDeviceAttribute(deviceId);
  186 + }
  187 + } catch (error) {}
93 } 188 }
94 return []; 189 return [];
95 }, 190 },
@@ -99,19 +194,19 @@ export const dataSourceSchema: FormSchema[] = [ @@ -99,19 +194,19 @@ export const dataSourceSchema: FormSchema[] = [
99 }, 194 },
100 }, 195 },
101 { 196 {
102 - field: 'deviceRename', 197 + field: DataSourceField.DEVICE_RENAME,
103 component: 'Input', 198 component: 'Input',
104 label: '设备', 199 label: '设备',
105 - colProps: { span: 4 }, 200 + colProps: { span: 8 },
106 componentProps: { 201 componentProps: {
107 placeholder: '设备重命名', 202 placeholder: '设备重命名',
108 }, 203 },
109 }, 204 },
110 { 205 {
111 - field: 'attributeRename', 206 + field: DataSourceField.ATTRIBUTE_RENAME,
112 component: 'Input', 207 component: 'Input',
113 label: '属性', 208 label: '属性',
114 - colProps: { span: 4 }, 209 + colProps: { span: 8 },
115 componentProps: { 210 componentProps: {
116 placeholder: '属性重命名', 211 placeholder: '属性重命名',
117 }, 212 },
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 - import { Button, PageHeader } from 'ant-design-vue'; 2 + import { Button, PageHeader, Empty } from 'ant-design-vue';
3 import { GridItem, GridLayout } from 'vue3-grid-layout'; 3 import { GridItem, GridLayout } from 'vue3-grid-layout';
4 import { nextTick, onMounted, ref } from 'vue'; 4 import { nextTick, onMounted, ref } from 'vue';
5 import WidgetWrapper from '../components/WidgetWrapper/WidgetWrapper.vue'; 5 import WidgetWrapper from '../components/WidgetWrapper/WidgetWrapper.vue';
@@ -254,7 +254,7 @@ @@ -254,7 +254,7 @@
254 </template> 254 </template>
255 <div> 255 <div>
256 <span class="mr-3 text-gray-400">已创建组件:</span> 256 <span class="mr-3 text-gray-400">已创建组件:</span>
257 - <span class="text-cyan-400"> {{ dataBoardList.length }}个</span> 257 + <span style="color: #409eff"> {{ dataBoardList.length }}个</span>
258 </div> 258 </div>
259 </PageHeader> 259 </PageHeader>
260 <section class="flex-1"> 260 <section class="flex-1">
@@ -307,6 +307,7 @@ @@ -307,6 +307,7 @@
307 </WidgetWrapper> 307 </WidgetWrapper>
308 </GridItem> 308 </GridItem>
309 </GridLayout> 309 </GridLayout>
  310 + <Empty v-if="!dataBoardList.length" />
310 </section> 311 </section>
311 <DataBindModal @register="register" @submit="getDataBoardComponent" /> 312 <DataBindModal @register="register" @submit="getDataBoardComponent" />
312 </section> 313 </section>
@@ -183,7 +183,7 @@ @@ -183,7 +183,7 @@
183 <div class="flex justify-between items-center"> 183 <div class="flex justify-between items-center">
184 <div>{{ item.name }}</div> 184 <div>{{ item.name }}</div>
185 <div class="flex content-center"> 185 <div class="flex content-center">
186 - <Statistic value="12"> 186 + <Statistic :value="item.componentNum">
187 <template #suffix> 187 <template #suffix>
188 <span class="text-sm">个组件</span> 188 <span class="text-sm">个组件</span>
189 </template> 189 </template>