Commit 66beda45970fc3a2b084db8583e0a1ff1434e306

Authored by xp.Huang
2 parents 17d679b2 f5633032

Merge branch 'perf/data-board' into 'main_dev'

perf: 优化数据看板控制组件&&数据源绑定

See merge request yunteng/thingskit-front!1179
Showing 59 changed files with 1308 additions and 1266 deletions
... ... @@ -6,6 +6,7 @@
6 6 ],
7 7 "cSpell.words": [
8 8 "ACKS",
  9 + "Cascader",
9 10 "clazz",
10 11 "Cmds",
11 12 "COAP",
... ...
... ... @@ -137,7 +137,7 @@ export const createOrEditDevice = (data) => {
137 137
138 138 // 查询设备详情
139 139 export const getDeviceDetail = (id: string) => {
140   - return defHttp.get({
  140 + return defHttp.get<DeviceRecord>({
141 141 url: DeviceManagerApi.DEVICE_URL + `/${id}`,
142 142 });
143 143 };
... ...
1 1 import { DataTypeEnum } from '/@/enums/objectModelEnum';
2   -import { FunctionType } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
  2 +import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
3 3
4 4 export interface Specs {
5 5 min: string;
... ... @@ -9,7 +9,7 @@ export interface Specs {
9 9
10 10 dataType?: string;
11 11 name?: string;
12   - value?: string;
  12 + value?: string | number;
13 13 step: string;
14 14 length: string;
15 15 boolOpen: string;
... ... @@ -26,6 +26,13 @@ export interface DataType {
26 26 specsList?: Specs[];
27 27 }
28 28
  29 +export interface ExtensionDesc {
  30 + zoomFactor?: number;
  31 + actionType?: string;
  32 + dataType: string;
  33 + registerAddress: number;
  34 +}
  35 +
29 36 export interface StructJSON {
30 37 functionName?: string;
31 38 identifier: string;
... ... @@ -46,7 +53,7 @@ export interface ModelOfMatterParams {
46 53 deviceProfileId?: string;
47 54 functionJson: FunctionJson;
48 55 functionName: string;
49   - functionType: FunctionType;
  56 + functionType: FunctionTypeEnum;
50 57 identifier: string;
51 58 remark: string;
52 59 id?: string;
... ... @@ -54,11 +61,11 @@ export interface ModelOfMatterParams {
54 61 callType?: string;
55 62 eventType?: string;
56 63 accessMode?: string;
57   - extensionDesc?: Recordable;
  64 + extensionDesc?: ExtensionDesc;
58 65 }
59 66
60 67 export interface GetModelTslParams {
61   - functionType: FunctionType;
  68 + functionType: FunctionTypeEnum;
62 69 deviceProfileId: string;
63 70 ifShowClass?: string | Boolean;
64 71 }
... ... @@ -85,3 +92,26 @@ export interface ModelOfMatterItemRecordType {
85 92 status: number;
86 93 deviceProfileId: string;
87 94 }
  95 +
  96 +export interface BatchGetObjectModelItemType {
  97 + id: string;
  98 + name: string;
  99 + transportType: string;
  100 + deviceType: string;
  101 + tsl: Tsl[];
  102 +}
  103 +
  104 +export interface Tsl {
  105 + functionName: string;
  106 + identifier: string;
  107 + functionType: string;
  108 + accessMode?: string;
  109 + specs?: {
  110 + dataType: DataType;
  111 + };
  112 + extensionDesc: ExtensionDesc;
  113 + eventType?: string;
  114 + outputData?: StructJSON[];
  115 + callType?: string;
  116 + inputData?: StructJSON[];
  117 +}
... ...
1 1 import { BasicPageParams } from '../model/baseModel';
2 2 import {
  3 + BatchGetObjectModelItemType,
3 4 GetModelTslParams,
4 5 ImportModelOfMatterType,
5 6 ModelOfMatterItemRecordType,
6 7 ModelOfMatterParams,
7 8 } from './model/modelOfMatterModel';
  9 +import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
8 10 import { defHttp } from '/@/utils/http/axios';
9   -import { FunctionType } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
10 11
11 12 enum ModelOfMatter {
12 13 CREATE = '/things_model',
... ... @@ -26,12 +27,14 @@ enum ModelOfMatter {
26 27
27 28 IMPORT_CSV = '/things_model/csvImport',
28 29 EXCEL_EXPORT = '/things_model/downloadTemplate',
  30 +
  31 + BATCH_GET_TSL_BY_DEVICE_PROFILES = '/things_model/batch/get_tsl',
29 32 }
30 33
31 34 export const getModelList = (
32 35 params: BasicPageParams & {
33 36 deviceProfileId?: string;
34   - functionTyp?: FunctionType;
  37 + functionTyp?: FunctionTypeEnum;
35 38 nameOrIdentifier?: string;
36 39 selectType?: string | undefined;
37 40 id?: string;
... ... @@ -181,3 +184,16 @@ export const excelExport = () => {
181 184 responseType: 'blob',
182 185 });
183 186 };
  187 +
  188 +export const batchGetObjectModel = ({
  189 + deviceProfileIds,
  190 + functionTypeEnum = 'all',
  191 +}: {
  192 + deviceProfileIds: string[];
  193 + functionTypeEnum?: FunctionTypeEnum | 'all';
  194 +}) => {
  195 + return defHttp.post<BatchGetObjectModelItemType[]>({
  196 + url: ModelOfMatter.BATCH_GET_TSL_BY_DEVICE_PROFILES,
  197 + data: { deviceProfileIds, functionTypeEnum },
  198 + });
  199 +};
... ...
... ... @@ -59,7 +59,7 @@ export interface GenModbusCommandType {
59 59 crc: string;
60 60 deviceCode: string;
61 61 method: string;
62   - registerAddress: string;
  62 + registerAddress: number;
63 63 registerNumber?: number;
64 64 registerValues?: number[];
65 65 }
... ...
... ... @@ -11,6 +11,7 @@ export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.v
11 11 export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue';
12 12 export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
13 13 export { default as ApiUpload } from './src/components/ApiUpload.vue';
  14 +export { default as ApiCascader } from './src/components/ApiCascader.vue';
14 15
15 16 export { default as StructForm } from './src/components/StructForm/StructForm.vue';
16 17 export { default as JavaScriptFunctionEditor } from './src/components/JavaScriptFunctionEditor.vue';
... ...
  1 +<script setup lang="ts">
  2 + import { Cascader } from 'ant-design-vue';
  3 + import { LoadingOutlined } from '@ant-design/icons-vue';
  4 + import { CascaderOptionType } from 'ant-design-vue/lib/cascader';
  5 + import { get } from 'lodash';
  6 + import { ref, unref, watch, watchEffect } from 'vue';
  7 + import { useRuleFormItem } from '/@/hooks/component/useFormItem';
  8 + import { useI18n } from '/@/hooks/web/useI18n';
  9 + import { isFunction } from '/@/utils/is';
  10 +
  11 + const { t } = useI18n();
  12 +
  13 + const props = withDefaults(
  14 + defineProps<{
  15 + value?: (string | number)[];
  16 + immediate?: boolean;
  17 + params?: any;
  18 + api?: Fn<any, Promise<any>>;
  19 + options?: CascaderOptionType[];
  20 + resultField?: string;
  21 + fieldNames?: Record<'label' | 'value' | 'children', string>;
  22 + }>(),
  23 + {
  24 + value: () => [],
  25 + immediate: true,
  26 + fieldNames: () => ({ label: 'label', value: 'value', children: 'children' }),
  27 + }
  28 + );
  29 +
  30 + const emit = defineEmits(['change', 'options-change', 'update:value']);
  31 +
  32 + const options = ref<CascaderOptionType[]>([]);
  33 + const loading = ref(false);
  34 + const isFirstLoad = ref(true);
  35 + const emitData = ref<any[]>([]);
  36 +
  37 + // Embedded in the form, just use the hook binding to perform form verification
  38 + const [state] = useRuleFormItem(props, 'value', 'update:value', emitData);
  39 +
  40 + watchEffect(() => {
  41 + props.immediate && fetch();
  42 + });
  43 +
  44 + watch(
  45 + () => props.params,
  46 + () => {
  47 + !unref(isFirstLoad) && fetch();
  48 + },
  49 + { deep: true }
  50 + );
  51 +
  52 + async function fetch() {
  53 + const api = props.api;
  54 + if (!api || !isFunction(api)) return;
  55 + options.value = [];
  56 + try {
  57 + loading.value = true;
  58 + const res = await api(props.params);
  59 + if (Array.isArray(res)) {
  60 + options.value = res;
  61 + emitChange();
  62 + return;
  63 + }
  64 + if (props.resultField) {
  65 + options.value = get(res, props.resultField) || [];
  66 + }
  67 + emitChange();
  68 + } catch (error) {
  69 + console.warn(error);
  70 + } finally {
  71 + loading.value = false;
  72 + }
  73 + }
  74 +
  75 + async function handleFetch(open) {
  76 + if (open) {
  77 + await fetch();
  78 + }
  79 + }
  80 +
  81 + function emitChange() {
  82 + emit('options-change', options);
  83 + }
  84 +
  85 + function handleChange(_, ...args) {
  86 + emitData.value = args;
  87 + }
  88 +</script>
  89 +
  90 +<template>
  91 + <Cascader
  92 + v-bind="$attrs"
  93 + @change="handleChange"
  94 + :fieldNames="fieldNames"
  95 + :options="options"
  96 + v-model:value="state"
  97 + @popupVisibleChange="handleFetch"
  98 + >
  99 + <template #notFoundContent v-if="loading">
  100 + <span>
  101 + <LoadingOutlined spin class="mr-1" />
  102 + {{ t('component.form.apiSelectNotFound') }}
  103 + </span>
  104 + </template>
  105 + </Cascader>
  106 +</template>
... ...
... ... @@ -117,6 +117,7 @@ export type ComponentType =
117 117 | 'ColorPicker'
118 118 | 'IconDrawer'
119 119 | 'ApiUpload'
  120 + | 'ApiCascader'
120 121 | 'ApiSearchSelect'
121 122 | 'StructForm'
122 123 | 'ApiSelectScrollLoad'
... ...
... ... @@ -7,6 +7,12 @@ export enum DataTypeEnum {
7 7 ENUM = 'ENUM',
8 8 }
9 9
  10 +export enum FunctionTypeEnum {
  11 + PROPERTIES = 'properties',
  12 + EVENTS = 'events',
  13 + SERVICE = 'services',
  14 +}
  15 +
10 16 export enum RegisterDataTypeEnum {
11 17 UN_SHORT = 'unshort',
12 18 }
... ... @@ -26,3 +32,7 @@ export enum RegisterActionTypeNameEnum {
26 32 INT = '06写入单个保持寄存器',
27 33 DOUBLE = '16写入多个保持寄存器',
28 34 }
  35 +
  36 +export enum ModbusCRCEnum {
  37 + CRC_16_LOWER = 'CRC_16_LOWER',
  38 +}
... ...
... ... @@ -2,8 +2,7 @@ import { BasicColumn, FormSchema } from '/@/components/Table';
2 2 import { findDictItemByCode } from '/@/api/system/dict';
3 3 import { h, unref } from 'vue';
4 4 import { Tooltip } from 'ant-design-vue';
5   -
6   -import { FunctionType } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
  5 +import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
7 6 import { useMessage } from '/@/hooks/web/useMessage';
8 7 import { useClipboard } from '@vueuse/core';
9 8 import { DictEnum } from '/@/enums/dictEnum';
... ... @@ -26,10 +25,10 @@ export const columns: BasicColumn[] = [
26 25 },
27 26 ];
28 27
29   -export const formatFunctionType: Record<FunctionType, string> = {
30   - [FunctionType.PROPERTIES]: '属性',
31   - [FunctionType.EVENTS]: '事件',
32   - [FunctionType.SERVICE]: '服务',
  28 +export const formatFunctionType: Record<FunctionTypeEnum, string> = {
  29 + [FunctionTypeEnum.PROPERTIES]: '属性',
  30 + [FunctionTypeEnum.EVENTS]: '事件',
  31 + [FunctionTypeEnum.SERVICE]: '服务',
33 32 };
34 33
35 34 const handleCopy = async (value: string) => {
... ... @@ -45,7 +44,7 @@ export const columnsDrawer: BasicColumn[] = [
45 44 title: '功能类型',
46 45 dataIndex: 'functionType',
47 46 width: 90,
48   - format: (text: FunctionType) => {
  47 + format: (text: FunctionTypeEnum) => {
49 48 return formatFunctionType[text];
50 49 },
51 50 },
... ...
... ... @@ -5,7 +5,8 @@ import { MessageEnum } from '/@/enums/messageEnum';
5 5 import { numberRule } from '/@/utils/rules';
6 6
7 7 import { deviceConfigGetRuleChain } from '/@/api/device/deviceConfigApi';
8   -import { FormField, FunctionType } from './step/cpns/physical/cpns/config';
  8 +import { FormField } from './step/cpns/physical/cpns/config';
  9 +import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
9 10 import { h, unref } from 'vue';
10 11 import { Tag, Tooltip } from 'ant-design-vue';
11 12 import { EventType, EventTypeColor, EventTypeName } from '../list/cpns/tabs/EventManage/config';
... ... @@ -68,10 +69,10 @@ export const steps = [
68 69 },
69 70 ];
70 71
71   -export const formatFunctionType: Record<FunctionType, string> = {
72   - [FunctionType.PROPERTIES]: '属性',
73   - [FunctionType.EVENTS]: '事件',
74   - [FunctionType.SERVICE]: '服务',
  72 +export const formatFunctionType: Record<FunctionTypeEnum, string> = {
  73 + [FunctionTypeEnum.PROPERTIES]: '属性',
  74 + [FunctionTypeEnum.EVENTS]: '事件',
  75 + [FunctionTypeEnum.SERVICE]: '服务',
75 76 };
76 77
77 78 export const physicalColumn: BasicColumn[] = [
... ... @@ -79,7 +80,7 @@ export const physicalColumn: BasicColumn[] = [
79 80 title: '功能类型',
80 81 dataIndex: FormField.FUNCTION_TYPE,
81 82 width: 90,
82   - format: (text: FunctionType) => {
  83 + format: (text: FunctionTypeEnum) => {
83 84 return formatFunctionType[text];
84 85 },
85 86 },
... ... @@ -153,9 +154,12 @@ export const modelOfMatterForm: FormSchema[] = [
153 154 colProps: { span: 8 },
154 155 componentProps: {
155 156 options: [
156   - { label: formatFunctionType[FunctionType.PROPERTIES], value: FunctionType.PROPERTIES },
157   - { label: formatFunctionType[FunctionType.EVENTS], value: FunctionType.EVENTS },
158   - { label: formatFunctionType[FunctionType.SERVICE], value: FunctionType.SERVICE },
  157 + {
  158 + label: formatFunctionType[FunctionTypeEnum.PROPERTIES],
  159 + value: FunctionTypeEnum.PROPERTIES,
  160 + },
  161 + { label: formatFunctionType[FunctionTypeEnum.EVENTS], value: FunctionTypeEnum.EVENTS },
  162 + { label: formatFunctionType[FunctionTypeEnum.SERVICE], value: FunctionTypeEnum.SERVICE },
159 163 ],
160 164 },
161 165 },
... ...
... ... @@ -156,7 +156,7 @@
156 156 import { isObject } from '/@/utils/is';
157 157 import { useRole } from '/@/hooks/business/useRole';
158 158 import { ExportModelCategory } from '/@/api/device/modelOfMatter';
159   - import { FunctionType } from './cpns/physical/cpns/config';
  159 + import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
160 160 import SelectImport from '../components/SelectImport.vue';
161 161
162 162 const { isPlatformAdmin, isSysadmin } = useRole();
... ... @@ -287,15 +287,15 @@
287 287 return Promise.all([
288 288 ExportModelCategory({
289 289 deviceProfileId,
290   - functionType: FunctionType.EVENTS,
  290 + functionType: FunctionTypeEnum.EVENTS,
291 291 }),
292 292 ExportModelCategory({
293 293 deviceProfileId,
294   - functionType: FunctionType.PROPERTIES,
  294 + functionType: FunctionTypeEnum.PROPERTIES,
295 295 }),
296 296 ExportModelCategory({
297 297 deviceProfileId,
298   - functionType: FunctionType.SERVICE,
  298 + functionType: FunctionTypeEnum.SERVICE,
299 299 }),
300 300 ]);
301 301 };
... ...
... ... @@ -23,24 +23,24 @@
23 23 v-model:activeKey="activeKey"
24 24 :size="size"
25 25 >
26   - <TabPane :key="FunctionType.PROPERTIES" tab="属性" />
27   - <TabPane :key="FunctionType.SERVICE" :disabled="isTCPGatewaySubDevice" tab="服务" />
28   - <TabPane :key="FunctionType.EVENTS" tab="事件" :disabled="isTCPGatewaySubDevice" />
  26 + <TabPane :key="FunctionTypeEnum.PROPERTIES" tab="属性" />
  27 + <TabPane :key="FunctionTypeEnum.SERVICE" :disabled="isTCPGatewaySubDevice" tab="服务" />
  28 + <TabPane :key="FunctionTypeEnum.EVENTS" tab="事件" :disabled="isTCPGatewaySubDevice" />
29 29 </Tabs>
30 30 <Attribute
31   - v-if="activeKey === FunctionType.PROPERTIES"
  31 + v-if="activeKey === FunctionTypeEnum.PROPERTIES"
32 32 :openModalMode="openModalMode"
33 33 :transportType="record?.transportType"
34 34 ref="AttrRef"
35 35 />
36 36 <Service
37   - v-if="activeKey === FunctionType.SERVICE"
  37 + v-if="activeKey === FunctionTypeEnum.SERVICE"
38 38 :record="$props.record"
39 39 :openModalMode="openModalMode"
40 40 ref="ServiceRef"
41 41 />
42 42 <Events
43   - v-if="activeKey === FunctionType.EVENTS"
  43 + v-if="activeKey === FunctionTypeEnum.EVENTS"
44 44 :openModalMode="openModalMode"
45 45 ref="EventsRef"
46 46 />
... ... @@ -69,7 +69,7 @@
69 69 import { DeviceRecord, DeviceTypeEnum } from '/@/api/device/model/deviceModel';
70 70 import { useMessage } from '/@/hooks/web/useMessage';
71 71 import { OpenModelMode, OpenModelOfMatterModelParams } from './types/index';
72   - import { FunctionType } from './cpns/config';
  72 + import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
73 73 import { useRole } from '/@/hooks/business/useRole';
74 74
75 75 const { isPlatformAdmin, isSysadmin } = useRole();
... ... @@ -87,7 +87,7 @@
87 87 });
88 88
89 89 const blockContent = `属性一般是指设备的运行状态,如当前温度等;服务是指设备可被调用的方法,支持定义参数,如执行某项任务;事件则是指设备上报的通知,如告警,需要被及时处理。`;
90   - const activeKey = ref<FunctionType>(FunctionType.PROPERTIES);
  90 + const activeKey = ref<FunctionTypeEnum>(FunctionTypeEnum.PROPERTIES);
91 91 const size = ref('small');
92 92
93 93 const AttrRef = ref<InstanceType<typeof Attribute>>();
... ... @@ -98,7 +98,7 @@
98 98 const openModalMode = ref<OpenModelMode>(OpenModelMode.CREATE);
99 99 const openModalParams = ref<OpenModelOfMatterModelParams>();
100 100
101   - const functionType = ref<FunctionType>();
  101 + const functionType = ref<FunctionTypeEnum>();
102 102 const { createMessage } = useMessage();
103 103
104 104 const setAttrFormData = (data: ModelOfMatterParams) => AttrRef.value?.setFormData(data);
... ... @@ -106,12 +106,12 @@
106 106 const setEventsFormData = (data: ModelOfMatterParams) => EventsRef.value?.setFormData(data);
107 107
108 108 const enums = {
109   - [FunctionType.PROPERTIES]: setAttrFormData,
110   - [FunctionType.SERVICE]: setServiceFormData,
111   - [FunctionType.EVENTS]: setEventsFormData,
  109 + [FunctionTypeEnum.PROPERTIES]: setAttrFormData,
  110 + [FunctionTypeEnum.SERVICE]: setServiceFormData,
  111 + [FunctionTypeEnum.EVENTS]: setEventsFormData,
112 112 };
113 113
114   - function setFormData(type: FunctionType, value: ModelOfMatterParams) {
  114 + function setFormData(type: FunctionTypeEnum, value: ModelOfMatterParams) {
115 115 const setFn = enums[type];
116 116 setFn(value);
117 117 }
... ... @@ -147,7 +147,7 @@
147 147 AttrRef.value?.resetFormData();
148 148 ServiceRef.value?.resetFormData();
149 149 EventsRef.value?.resetFormData();
150   - activeKey.value = FunctionType.PROPERTIES;
  150 + activeKey.value = FunctionTypeEnum.PROPERTIES;
151 151 if (flag) {
152 152 closeModal();
153 153 }
... ... @@ -158,9 +158,9 @@
158 158 setModalProps({ loading: false, okButtonProps: { loading: true } });
159 159
160 160 let params: Partial<ModelOfMatterParams>;
161   - if (activeKey.value == FunctionType.PROPERTIES) {
  161 + if (activeKey.value == FunctionTypeEnum.PROPERTIES) {
162 162 params = (await AttrRef.value?.getFormData()) || {};
163   - } else if (activeKey.value == FunctionType.SERVICE) {
  163 + } else if (activeKey.value == FunctionTypeEnum.SERVICE) {
164 164 params = (await ServiceRef.value?.getFormData()) || {};
165 165 } else {
166 166 params = (await EventsRef.value?.getFormData()) || {};
... ...
... ... @@ -24,7 +24,7 @@
24 24 import { DeviceRecord } from '/@/api/device/model/deviceModel';
25 25 import { Button } from 'ant-design-vue';
26 26 import { getModelTsl } from '/@/api/device/modelOfMatter';
27   - import { FunctionType } from './cpns/config';
  27 + import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
28 28 import { isObject } from '/@/utils/is';
29 29
30 30 defineEmits(['register']);
... ... @@ -75,15 +75,15 @@
75 75 return Promise.all([
76 76 getModelTsl({
77 77 deviceProfileId,
78   - functionType: FunctionType.EVENTS,
  78 + functionType: FunctionTypeEnum.EVENTS,
79 79 }),
80 80 getModelTsl({
81 81 deviceProfileId,
82   - functionType: FunctionType.PROPERTIES,
  82 + functionType: FunctionTypeEnum.PROPERTIES,
83 83 }),
84 84 getModelTsl({
85 85 deviceProfileId,
86   - functionType: FunctionType.SERVICE,
  86 + functionType: FunctionTypeEnum.SERVICE,
87 87 }),
88 88 ]);
89 89 };
... ...
... ... @@ -6,7 +6,7 @@
6 6 transfromToStructJSON,
7 7 excludeIdInStructJSON,
8 8 } from '/@/components/Form/src/components/StructForm/util';
9   - import { FunctionType } from './config';
  9 + import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
10 10 import { isArray } from 'lodash';
11 11 import { OpenModelMode } from '../types';
12 12 import { formSchemas } from '/@/components/Form/src/components/StructForm/config';
... ... @@ -46,7 +46,7 @@
46 46
47 47 const value = {
48 48 functionName,
49   - functionType: FunctionType.PROPERTIES,
  49 + functionType: FunctionTypeEnum.PROPERTIES,
50 50 remark,
51 51 identifier,
52 52 accessMode,
... ...
... ... @@ -3,7 +3,8 @@
3 3 </template>
4 4 <script lang="ts" setup>
5 5 import { BasicForm, useForm } from '/@/components/Form';
6   - import { eventSchemas, FunctionType } from './config';
  6 + import { eventSchemas } from './config';
  7 + import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
7 8 import { ModelOfMatterParams, StructJSON } from '/@/api/device/model/modelOfMatterModel';
8 9 import { StructFormValue } from '/@/components/Form/src/components/StructForm/type';
9 10 import { excludeIdInStructJSON } from '/@/components/Form/src/components/StructForm/util';
... ... @@ -57,7 +58,7 @@
57 58 functionName,
58 59 identifier,
59 60 remark,
60   - functionType: FunctionType.EVENTS,
  61 + functionType: FunctionTypeEnum.EVENTS,
61 62 eventType,
62 63 functionJson: {
63 64 outputData,
... ...
... ... @@ -4,7 +4,7 @@
4 4 <script lang="ts" setup>
5 5 import { BasicForm, useForm } from '/@/components/Form';
6 6 import { FormField, serviceSchemas } from './config';
7   - import { FunctionType } from './config';
  7 + import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
8 8 import { StructFormValue } from '/@/components/Form/src/components/StructForm/type';
9 9 import { ModelOfMatterParams, StructJSON } from '/@/api/device/model/modelOfMatterModel';
10 10 import { DeviceRecord } from '/@/api/device/model/deviceModel';
... ... @@ -94,7 +94,7 @@
94 94 functionName,
95 95 identifier,
96 96 remark,
97   - functionType: FunctionType.SERVICE,
  97 + functionType: FunctionTypeEnum.SERVICE,
98 98 callType,
99 99 functionJson: {
100 100 inputData,
... ...
... ... @@ -9,9 +9,9 @@
9 9 </div>
10 10 <div>
11 11 <Tabs type="card" v-model:active-key="activeKey" @change="handleSwitchTsl">
12   - <Tabs.TabPane :key="FunctionType.PROPERTIES" tab="属性" />
13   - <Tabs.TabPane :key="FunctionType.SERVICE" tab="服务" />
14   - <Tabs.TabPane :key="FunctionType.EVENTS" tab="事件" />
  12 + <Tabs.TabPane :key="FunctionTypeEnum.PROPERTIES" tab="属性" />
  13 + <Tabs.TabPane :key="FunctionTypeEnum.SERVICE" tab="服务" />
  14 + <Tabs.TabPane :key="FunctionTypeEnum.EVENTS" tab="事件" />
15 15 <template #tabBarExtraContent>
16 16 <Button class="ml-2" @click="handleCopy">
17 17 <template #icon>
... ... @@ -38,7 +38,7 @@
38 38 import { CopyOutlined } from '@ant-design/icons-vue';
39 39 import { useMessage } from '/@/hooks/web/useMessage';
40 40 import { Button, Typography, TypographyParagraph, Tabs, Spin, Input } from 'ant-design-vue';
41   - import { FunctionType } from './config';
  41 + import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
42 42 import useCommon from '../hook/useCommon';
43 43 import { DeviceRecord } from '/@/api/device/model/deviceModel';
44 44 import { getModelTsl } from '/@/api/device/modelOfMatter';
... ... @@ -56,7 +56,7 @@
56 56
57 57 const jsonValue = ref();
58 58
59   - const activeKey = ref(FunctionType.PROPERTIES);
  59 + const activeKey = ref(FunctionTypeEnum.PROPERTIES);
60 60
61 61 const { copied, copy } = useClipboard({ legacy: true });
62 62 const handleCopy = async () => {
... ... @@ -81,7 +81,7 @@
81 81 jsonValue.value = null;
82 82 };
83 83
84   - const handleSwitchTsl = async (functionType: FunctionType) => {
  84 + const handleSwitchTsl = async (functionType: FunctionTypeEnum) => {
85 85 try {
86 86 loading.value = true;
87 87 const record = await getModelTsl({
... ... @@ -102,8 +102,8 @@
102 102 };
103 103
104 104 onMounted(() => {
105   - activeKey.value = FunctionType.PROPERTIES;
106   - handleSwitchTsl(FunctionType.PROPERTIES);
  105 + activeKey.value = FunctionTypeEnum.PROPERTIES;
  106 + handleSwitchTsl(FunctionTypeEnum.PROPERTIES);
107 107 });
108 108
109 109 defineExpose({
... ...
... ... @@ -33,17 +33,6 @@ export enum FormField {
33 33 HAS_STRUCT_FROM = 'hasStructForm',
34 34 }
35 35
36   -export enum FunctionType {
37   - PROPERTIES = 'properties',
38   - EVENTS = 'events',
39   - SERVICE = 'services',
40   -}
41   -
42   -export enum AssessMode {
43   - READ = 'r',
44   - WRITE = 'w',
45   -}
46   -
47 36 const isNumber = (type: string) => {
48 37 return type === DataTypeEnum.NUMBER_INT || type === DataTypeEnum.NUMBER_DOUBLE;
49 38 };
... ...
... ... @@ -27,7 +27,7 @@ import { DeviceModelOfMatterAttrs, DeviceProfileModel } from '/@/api/device/mode
27 27 import TriggerDurationInput from './TriggerDurationInput.vue';
28 28 import { DataTypeEnum } from '/@/enums/objectModelEnum';
29 29 import { getModelTsl } from '/@/api/device/modelOfMatter';
30   -import { FunctionType } from '/@/views/device/profiles/step/cpns/physical/cpns/config';
  30 +import { FunctionTypeEnum } from '/@/enums/objectModelEnum';
31 31 import { GetModelTslParams } from '/@/api/device/model/modelOfMatterModel';
32 32 import { FlipFlopComponentTypeEnum } from './types';
33 33 import { OptionsType } from 'ant-design-vue/es/vc-select/interface';
... ... @@ -405,7 +405,7 @@ export const getFormSchemas = (
405 405 labelField: 'functionName',
406 406 valueField: 'identifier',
407 407 params: {
408   - functionType: FunctionType.EVENTS,
  408 + functionType: FunctionTypeEnum.EVENTS,
409 409 deviceProfileId: formModel[FormFieldEnum.DEVICE_PROFILE_ID],
410 410 },
411 411 placeholder: `请选择${FormFieldNameEnum.DEVICE_EVENT_TRIGGER_KEY}`,
... ...
... ... @@ -37,5 +37,15 @@
37 37 </script>
38 38
39 39 <template>
40   - <BasicForm @register="register" />
  40 + <BasicForm @register="register" class="data-board-source-form" />
41 41 </template>
  42 +
  43 +<style lang="less" scoped>
  44 + .data-board-source-form {
  45 + :deep(.ant-form-item-control-input-content) {
  46 + > div > div {
  47 + width: 100%;
  48 + }
  49 + }
  50 + }
  51 +</style>
... ...
... ... @@ -197,8 +197,7 @@
197 197 <label class="w-24 text-right pr-2">数据源{{ index + 1 }}</label>
198 198 <component
199 199 :ref="(event) => setDataSourceFormsEl(item.uuid, event, index)"
200   - class="flex-1 bg-light-50 dark:bg-dark-400"
201   - style="max-width: calc(100% - 216px)"
  200 + class="flex-1 bg-light-50 dark:bg-dark-400 data-board-source-form"
202 201 :is="getComponent"
203 202 :component-config="componentConfig"
204 203 :values="item"
... ... @@ -238,9 +237,11 @@
238 237 </template>
239 238
240 239 <style scoped lang="less">
241   - :deep(#deviceId) {
242   - div {
243   - width: 100%;
  240 + .data-board-source-form {
  241 + :deep(.ant-form-item-control-input-content) {
  242 + > div > div {
  243 + width: 100%;
  244 + }
244 245 }
245 246 }
246 247 </style>
... ...
1 1 <script lang="ts" setup>
2 2 import { computed } from 'vue';
3   - import { PackagesCategoryEnum } from '../../../packages/index.type';
4 3 import { SelectedWidgetKeys } from '../../index.type';
5 4 import { Alert } from 'ant-design-vue';
6 5
... ... @@ -9,10 +8,10 @@
9 8 }>();
10 9
11 10 const alert = {
12   - [PackagesCategoryEnum.MAP]: [
13   - '1、绑定数据源为结构体时,可以自行选择结构体里的属性作为经纬度',
14   - '2、绑定数据源为非结构体时,第一数据源为经度,第二数据源为纬度,且数据源为同一设备,并同时上报。否则地图组件不能正常显示。',
15   - ],
  11 + // [PackagesCategoryEnum.MAP]: [
  12 + // '1、绑定数据源为结构体时,可以自行选择结构体里的属性作为经纬度',
  13 + // '2、绑定数据源为非结构体时,第一数据源为经度,第二数据源为纬度,且数据源为同一设备,并同时上报。否则地图组件不能正常显示。',
  14 + // ],
16 15 };
17 16
18 17 const getMessage = computed(() => {
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
  2 + import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4 4 import { Spin } from 'ant-design-vue';
5 5 import { computed, ref, unref } from 'vue';
6 6 import { useComponentScale } from '../../../hook/useComponentScale';
7   - import { useSendCommand } from '../../../hook/useSendCommand';
8 7 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
9 8 import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
10 9 import { useDataFetch } from '../../../hook/socket/useSocket';
11   - import { getSendValues } from '../config';
12 10 import { useModal } from '/@/components/Modal';
13 11 import PasswordModal from '../component/PasswordModal.vue';
  12 + import { useCommandDelivery } from '../../../hook/useCommandDelivery';
  13 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
14 14
15 15 const props = defineProps<{
16 16 config: ComponentPropsConfigType<typeof option>;
... ... @@ -20,48 +20,31 @@
20 20
21 21 const currentValue = ref(false);
22 22
  23 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
  24 +
23 25 const getDesign = computed(() => {
24 26 const { option, persetOption } = props.config;
25   - const {
26   - attribute,
27   - attributeRename,
28   - attributeName,
29   - componentInfo,
30   - commandType,
31   - extensionDesc,
32   - codeType,
33   - deviceCode,
34   - customCommand,
35   - } = option;
  27 + const { attribute, attributeRename, componentInfo, commandType, deviceProfileId } = option;
36 28
37 29 const { fontSize: persetFontSize, password: persetPassword } = persetOption || {};
38 30 const { fontSize, password } = componentInfo || {};
  31 +
  32 + const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
39 33 return {
40   - attribute: attributeRename || attributeName || attribute,
  34 + attribute: attributeRename || tsl?.functionName || attribute,
41 35 fontSize: fontSize || persetFontSize || 14,
42 36 password: password || persetPassword,
43   - extensionDesc: extensionDesc ? JSON.parse(extensionDesc) : {},
44 37 commandType,
45   - codeType,
46   - deviceCode,
47   - customCommand,
48 38 };
49 39 });
50 40
51   - const { sendCommand, loading } = useSendCommand();
  41 + const { doCommandDeliver, loading } = useCommandDelivery();
52 42
53   - const handleSendCommand = async (data) => {
54   - const { control: event } = data || {};
55   - const target = event.target as HTMLInputElement;
56   - const value = !target.checked;
  43 + const handleSendCommand = async () => {
  44 + if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return;
57 45 const { option } = props.config || {};
58   -
59   - const { values, isModbusCommand, sendValue } =
60   - (await getSendValues(option, unref(getDesign), value)) || {};
61   -
62   - const flag = await sendCommand(values, isModbusCommand ? sendValue : value, isModbusCommand);
63   - if (flag) currentValue.value = value;
64   - flag ? (currentValue.value = value) : (target.checked = !value);
  46 + const result = await doCommandDeliver(option, Number(!unref(currentValue)));
  47 + currentValue.value = result ? !unref(currentValue) : unref(currentValue);
65 48 };
66 49
67 50 const handleChange = async (event: Event) => {
... ... @@ -74,16 +57,7 @@
74 57 return;
75 58 }
76 59
77   - const target = event.target as HTMLInputElement;
78   - const value = target.checked;
79   - const { option } = props.config || {};
80   -
81   - const { values, isModbusCommand, sendValue } =
82   - (await getSendValues(option, unref(getDesign), value)) || {};
83   -
84   - const flag = await sendCommand(values, isModbusCommand ? sendValue : value, isModbusCommand);
85   - if (flag) currentValue.value = value;
86   - flag ? (currentValue.value = value) : (target.checked = !value);
  60 + handleSendCommand();
87 61 };
88 62
89 63 const updateFn: DataFetchUpdateFn = (message, attribute) => {
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
  2 + import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4 4 import { SvgIcon } from '/@/components/Icon';
5 5 import { Switch } from 'ant-design-vue';
6 6 import { computed, ref } from 'vue';
7 7 import { useComponentScale } from '../../../hook/useComponentScale';
8   - import { useSendCommand } from '../../../hook/useSendCommand';
9 8 import { unref } from 'vue';
10 9 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
11 10 import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
12 11 import { useDataFetch } from '../../../hook/socket/useSocket';
13   - import { getSendValues } from '../config';
14 12 import { useModal } from '/@/components/Modal';
15 13 import PasswordModal from '../component/PasswordModal.vue';
  14 + import { useCommandDelivery } from '../../../hook/useCommandDelivery';
  15 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
16 16
17 17 const props = defineProps<{
18 18 config: ComponentPropsConfigType<typeof option>;
... ... @@ -20,19 +20,11 @@
20 20
21 21 const checked = ref(false);
22 22
  23 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
  24 +
23 25 const getDesign = computed(() => {
24 26 const { option, persetOption } = props.config;
25   - const {
26   - componentInfo,
27   - attribute,
28   - attributeRename,
29   - attributeName,
30   - commandType,
31   - extensionDesc,
32   - codeType,
33   - deviceCode,
34   - customCommand,
35   - } = option;
  27 + const { componentInfo, attribute, deviceProfileId, attributeRename, commandType } = option;
36 28 const {
37 29 icon: presetIcon,
38 30 iconColor: presetIconColor,
... ... @@ -41,21 +33,18 @@
41 33 } = persetOption || {};
42 34 const { icon, iconColor, fontSize, password } = componentInfo || {};
43 35
  36 + const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
44 37 return {
45 38 icon: icon ?? presetIcon,
46 39 iconColor: iconColor || presetIconColor,
47   - attribute: attributeRename || attributeName || attribute,
48   - extensionDesc: extensionDesc ? JSON.parse(extensionDesc) : {},
  40 + attribute: attributeRename || tsl?.functionName || attribute,
49 41 fontSize: fontSize || persetFontSize || 14,
50 42 password: password || persetPassword,
51 43 commandType,
52   - codeType,
53   - deviceCode,
54   - customCommand,
55 44 };
56 45 });
57 46
58   - const { sendCommand, loading } = useSendCommand();
  47 + const { loading, doCommandDeliver } = useCommandDelivery();
59 48
60 49 const handleChange = async () => {
61 50 if (unref(getDesign).password) {
... ... @@ -63,32 +52,14 @@
63 52 checked.value = !unref(checked);
64 53 return;
65 54 }
66   - const { option } = props.config || {};
67   -
68   - const { values, isModbusCommand, sendValue } =
69   - (await getSendValues(option, unref(getDesign), unref(checked))) || {};
70   -
71   - const flag = await sendCommand(
72   - values,
73   - isModbusCommand ? sendValue : unref(checked),
74   - isModbusCommand
75   - );
76   - if (!flag) checked.value = !unref(checked);
  55 + handleSendCommand();
77 56 };
78 57
79 58 const handleSendCommand = async () => {
  59 + if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return;
80 60 const { option } = props.config || {};
81   - checked.value = !unref(checked);
82   -
83   - const { values, isModbusCommand, sendValue } =
84   - (await getSendValues(option, unref(getDesign), unref(checked))) || {};
85   -
86   - const flag = await sendCommand(
87   - values,
88   - isModbusCommand ? sendValue : unref(checked),
89   - isModbusCommand
90   - );
91   - if (!flag) checked.value = !unref(checked);
  61 + const result = await doCommandDeliver(option, Number(unref(checked)));
  62 + if (!result) checked.value = !unref(checked);
92 63 };
93 64
94 65 const updateFn: DataFetchUpdateFn = (message, attribute) => {
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
  2 + import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4 4 import { Spin } from 'ant-design-vue';
5 5 import { computed, ref, unref } from 'vue';
6 6 import { useComponentScale } from '../../../hook/useComponentScale';
7   - import { useSendCommand } from '../../../hook/useSendCommand';
8 7 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
9 8 import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
10 9 import { useDataFetch } from '../../../hook/socket/useSocket';
11   - import { getSendValues } from '../config';
12 10 import PasswordModal from '../component/PasswordModal.vue';
13 11 import { useModal } from '/@/components/Modal';
  12 + import { useCommandDelivery } from '../../../hook/useCommandDelivery';
  13 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
14 14
15 15 const props = defineProps<{
16 16 config: ComponentPropsConfigType<typeof option>;
... ... @@ -20,34 +20,31 @@
20 20
21 21 const currentValue = ref(false);
22 22
  23 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
  24 +
23 25 const getDesign = computed(() => {
24 26 const { option, persetOption } = props.config;
25   - const {
26   - attribute,
27   - attributeRename,
28   - attributeName,
29   - commandType,
30   - extensionDesc,
31   - codeType,
32   - deviceCode,
33   - customCommand,
34   - componentInfo,
35   - } = option;
  27 + const { attribute, deviceProfileId, attributeRename, commandType, componentInfo } = option;
36 28 const { fontSize: persetFontSize, password: persetPassword } = persetOption || {};
37 29 const { fontSize, password } = componentInfo || {};
  30 +
  31 + const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
  32 +
38 33 return {
39   - attribute: attributeRename || attributeName || attribute,
40   - extensionDesc: extensionDesc ? JSON.parse(extensionDesc) : {},
  34 + attribute: attributeRename || tsl?.functionName || attribute,
41 35 password: password || persetPassword,
42 36 commandType,
43   - codeType,
44   - deviceCode,
45   - customCommand,
46 37 fontSize: fontSize || persetFontSize || 14,
47 38 };
48 39 });
  40 + const { doCommandDeliver, loading } = useCommandDelivery();
49 41
50   - const { loading, sendCommand } = useSendCommand();
  42 + const handleSendCommand = async () => {
  43 + if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return;
  44 + const { option } = props.config || {};
  45 + const result = await doCommandDeliver(option, Number(!unref(currentValue)));
  46 + currentValue.value = result ? !unref(currentValue) : unref(currentValue);
  47 + };
51 48 const handleChange = async (event: Event) => {
52 49 if (unref(getDesign).password) {
53 50 const target = event.target as HTMLInputElement;
... ... @@ -57,29 +54,7 @@
57 54 openModal(true, { password: unref(getDesign).password, control: event });
58 55 return;
59 56 }
60   -
61   - const target = event.target as HTMLInputElement;
62   - const value = target.checked;
63   - const { option } = props.config || {};
64   -
65   - const { values, isModbusCommand, sendValue } =
66   - (await getSendValues(option, unref(getDesign), value)) || {};
67   -
68   - const flag = await sendCommand(values, isModbusCommand ? sendValue : value, isModbusCommand);
69   - flag ? (currentValue.value = value) : (target.checked = !value);
70   - };
71   -
72   - const handleSendCommand = async (data) => {
73   - const { control: event } = data || {};
74   - const target = event.target as HTMLInputElement;
75   - const value = !target.checked;
76   - const { option } = props.config || {};
77   -
78   - const { values, isModbusCommand, sendValue } =
79   - (await getSendValues(option, unref(getDesign), value)) || {};
80   -
81   - const flag = await sendCommand(values, isModbusCommand ? sendValue : value, isModbusCommand);
82   - flag ? (currentValue.value = value) : (target.checked = !value);
  57 + handleSendCommand();
83 58 };
84 59
85 60 const updateFn: DataFetchUpdateFn = (message, attribute) => {
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
  2 + import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4 4 import { Slider, Spin } from 'ant-design-vue';
5 5 import { computed, ref, unref } from 'vue';
6 6 import { useComponentScale } from '../../../hook/useComponentScale';
7 7 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
8   - import { useSendCommand } from '../../../hook/useSendCommand';
9 8 import { useReceiveValue } from '../../../hook/useReceiveValue';
10 9 import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
11 10 import { useDataFetch } from '../../../hook/socket/useSocket';
12   - import { TaskTypeEnum } from '/@/views/task/center/config';
13   - import { useMessage } from '/@/hooks/web/useMessage';
14   - import { SingleToHex } from '/@/views/device/list/cpns/tabs/ObjectModelCommandDeliveryModal/config';
15   - import { genModbusCommand } from '/@/api/task';
16 11 import PasswordModal from '../component/PasswordModal.vue';
17 12 import { useModal } from '/@/components/Modal';
  13 + import { useCommandDelivery } from '../../../hook/useCommandDelivery';
  14 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
18 15
19 16 const props = defineProps<{
20 17 config: ComponentPropsConfigType<typeof option>;
21 18 }>();
22 19
23 20 const sliderValue = ref<number>(33);
24   - const oldSliderValue = ref<number>(33);
25   - const noSendValue = ref<number>(0);
26   - const sliderEl = ref<Nullable<InstanceType<typeof Slider>>>(null);
  21 + const sliderElRef = ref<InstanceType<typeof Slider>>();
27 22
28   - const { loading, sendCommand } = useSendCommand();
  23 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
29 24
30 25 const getDesign = computed(() => {
31 26 const { option, persetOption } = props.config;
32   - const {
33   - componentInfo,
34   - attribute,
35   - attributeRename,
36   - attributeName,
37   - commandType,
38   - extensionDesc,
39   - codeType,
40   - deviceCode,
41   - customCommand,
42   - } = option;
  27 + const { componentInfo, attribute, deviceProfileId, attributeRename, commandType } = option;
  28 +
43 29 const {
44 30 controlBarColor: persetControlBarColor,
45 31 fonColor: persetFontColor,
... ... @@ -50,6 +36,8 @@
50 36 fontSize: persetFontSize,
51 37 password: persetPassword,
52 38 } = persetOption || {};
  39 +
  40 + const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
53 41 const {
54 42 controlBarColor,
55 43 password,
... ... @@ -61,16 +49,12 @@
61 49 fontSize,
62 50 } = componentInfo || {};
63 51 return {
64   - attribute: attributeRename || attributeName || attribute,
  52 + attribute: attributeRename || tsl?.functionName || attribute,
65 53 controlBarColor: controlBarColor ?? persetControlBarColor,
66 54 fontColor: fontColor ?? persetFontColor,
67 55 minNumber: minNumber ?? persetMinNumber,
68 56 maxNumber: maxNumber ?? persetMaxNumber,
69   - extensionDesc: extensionDesc ? JSON.parse(extensionDesc) : {},
70 57 commandType,
71   - codeType,
72   - deviceCode,
73   - customCommand,
74 58 textColor: textColor || persetTextColor,
75 59 valueSize: valueSize || persetValueSize || 20,
76 60 fontSize: fontSize || persetFontSize || 14,
... ... @@ -78,153 +62,28 @@
78 62 };
79 63 });
80 64
81   - const index = ref<number>(0);
82   - const afterValue = ref<number>(0); //点击Slider获取的值
83   -
84   - const handleChange = async (e) => {
85   - index.value++;
86   - afterValue.value = e;
87   - if (index.value == 1) {
88   - oldSliderValue.value = unref(sliderValue);
89   - return; //这个是因为设置了最大值时,当sliderValue的值大于最大值时只会显示设置的最大值,会执行一次change
90   - }
91   - sliderValue.value = e;
92   - };
93   -
94   - const handleAfterChange = async () => {
95   - unref(sliderEl)?.blur();
96   - if (unref(sliderValue) == unref(afterValue)) return;
97   - if (unref(getDesign).password) return;
98   - sliderValue.value = afterValue.value; //这一步是因为当我们是点击不是拖动时候,会让值更新不了,所以需要赋值一次
99   - };
100   -
101   - const { createMessage } = useMessage();
102   -
103   - // 获取小数
104   - const getFloatPart = (number: string | number) => {
105   - const isLessZero = Number(number) < 0;
106   - number = number.toString();
107   - const floatPartStartIndex = number.indexOf('.');
108   - const value = ~floatPartStartIndex
109   - ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}`
110   - : '0';
111   - return Number(value);
  65 + const sendValue = ref(0);
  66 + const handleChange = async (value: number) => {
  67 + sendValue.value = value;
112 68 };
113 69
114   - const getArray = (values) => {
115   - const str = values.replace(/\s+/g, '');
116   - const array: any = [];
  70 + const { loading, doCommandDeliver } = useCommandDelivery();
117 71
118   - for (let i = 0; i < str.length; i += 4) {
119   - const chunk = parseInt(str.substring(i, i + 4), 16);
120   - array.push(chunk);
121   - }
122   - return array;
123   - };
124   -
125   - const getSendValue = async (value: number) => {
126   - const { extensionDesc, codeType, deviceCode, customCommand } = unref(getDesign) || {};
127   - const { transportType } = customCommand || {};
128   - const { registerAddress, actionType, zoomFactor } = extensionDesc || {};
129   - const newZoomValue = zoomFactor ? Number(zoomFactor) : 1;
130   - if (transportType == 'TCP' && codeType == TaskTypeEnum.MODBUS_RTU) {
131   - if (!deviceCode) {
132   - createMessage.warning('当前设备没有设置地址码');
133   - return;
134   - }
135   - const modbusForm = ref({
136   - crc: 'CRC_16_LOWER',
137   - deviceCode: deviceCode,
138   - method: actionType == '16' ? '10' : actionType,
139   - registerAddress,
140   - registerNumber: 1,
141   - registerValues: [value],
142   - }) as any;
143   - if (!actionType) {
144   - createMessage.warning('当前物模型扩展描述没有填写');
145   - return;
146   - }
147   - if (actionType == '06') {
148   - const newValue = Math.trunc(value) * newZoomValue + getFloatPart(value) * newZoomValue;
149   - if (newValue % 1 != 0) {
150   - createMessage.warning(`值必须是整数,缩放因子为${unref(newZoomValue)}`);
151   - return;
152   - }
153   -
154   - if (newValue > 65535) {
155   - createMessage.warning(`值不能超过65535,缩放因子是${unref(newZoomValue)}`);
156   - return;
157   - }
158   - modbusForm.value.registerValues = [newValue];
159   - }
160   - if (actionType == '05') {
161   - if (Number(value) != 0 || Number(value) != 1) {
162   - createMessage.warning(`当前物模型仅支持值为0和1`);
163   - return;
164   - }
165   - }
166   - if (actionType == '16' || actionType == '10') {
167   - const regex = /^-?\d+(\.\d{0,2})?$/;
168   - const values =
169   - Math.trunc(value) * unref(newZoomValue) + getFloatPart(value) * unref(newZoomValue);
170   - if (!regex.test(values as any)) {
171   - createMessage.warning(`值精确到两位小数,缩放因子是${unref(newZoomValue)}`);
172   - return;
173   - }
174   - const newValue = values == 0 ? [0, 0] : getArray(SingleToHex(values));
175   - modbusForm.value.registerValues = newValue;
176   - modbusForm.value.registerNumber = 2;
177   - modbusForm.value.method = '10';
178   - }
179   - const sendValue = await genModbusCommand(unref(modbusForm));
180   - return sendValue;
181   - }
182   - };
183   -
184   - const handleBlur = async () => {
  72 + const handleAfterChange = () => {
185 73 if (unref(getDesign).password) {
186   - sliderValue.value = oldSliderValue.value;
187   - openModal(true, { password: unref(getDesign).password, value: afterValue.value });
  74 + openModal(true, { password: unref(getDesign).password });
188 75 return;
189 76 }
190   -
191   - if (unref(oldSliderValue) !== unref(sliderValue)) {
192   - const { codeType, customCommand } = unref(getDesign) || {};
193   - const { transportType } = customCommand || {};
194   - const sendValue = ref<any>(unref(sliderValue));
195   - const isModbusCommand = ref<boolean>(false);
196   - if (transportType == 'TCP' && codeType == TaskTypeEnum.MODBUS_RTU) {
197   - sendValue.value = await getSendValue(unref(sliderValue));
198   - isModbusCommand.value = true;
199   - }
200   - const flag = await sendCommand(props.config.option, unref(sendValue), unref(isModbusCommand));
201   - flag
202   - ? ((sliderValue.value = unref(sliderValue)),
203   - (oldSliderValue.value = sliderValue.value),
204   - (index.value = 0))
205   - : (sliderValue.value = unref(oldSliderValue));
206   - }
  77 + handleSendCommand();
207 78 };
208 79
209   - const handleSendCommand = async (data) => {
210   - const { value } = data || {};
211   - sliderValue.value = value;
212   - if (unref(oldSliderValue) !== unref(sliderValue)) {
213   - const { codeType, customCommand } = unref(getDesign) || {};
214   - const { transportType } = customCommand || {};
215   - const sendValue = ref<any>(unref(sliderValue));
216   - const isModbusCommand = ref<boolean>(false);
217   - if (transportType == 'TCP' && codeType == TaskTypeEnum.MODBUS_RTU) {
218   - sendValue.value = await getSendValue(unref(sliderValue));
219   - isModbusCommand.value = true;
220   - }
221   - const flag = await sendCommand(props.config.option, unref(sendValue), unref(isModbusCommand));
222   - flag
223   - ? ((sliderValue.value = unref(sliderValue)),
224   - (oldSliderValue.value = sliderValue.value),
225   - (index.value = 0))
226   - : (sliderValue.value = unref(oldSliderValue));
227   - }
  80 + const handleSendCommand = async () => {
  81 + if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return;
  82 + const value = unref(sendValue);
  83 + const { option } = props.config || {};
  84 + const result = await doCommandDeliver(option, value);
  85 + unref(sliderElRef)?.blur();
  86 + sliderValue.value = result ? value : unref(sliderValue);
228 87 };
229 88
230 89 const { getNumberValue } = useReceiveValue();
... ... @@ -233,7 +92,6 @@
233 92 const [latest] = data[attribute] || [];
234 93 const [name, value] = latest;
235 94 if (!name && !value) return;
236   - noSendValue.value = getNumberValue(value);
237 95 sliderValue.value = getNumberValue(value);
238 96 };
239 97
... ... @@ -254,10 +112,11 @@
254 112 fontSize: (getRatio ? getRatio * getDesign.valueSize : getDesign.valueSize) + 'px',
255 113 }"
256 114 class="font-bold text-xl mt-3 text-center"
257   - >{{ sliderValue }}</span
258 115 >
  116 + {{ sliderValue }}
  117 + </span>
259 118 <Slider
260   - ref="sliderEl"
  119 + ref="sliderElRef"
261 120 :style="{
262 121 '--slider-color': getDesign.controlBarColor,
263 122 '--slider-handle': (getRatio ? getRatio * 14 : 14) + 'px',
... ... @@ -270,7 +129,6 @@
270 129 :max="getDesign.maxNumber"
271 130 @change="handleChange"
272 131 @afterChange="handleAfterChange"
273   - @blur="handleBlur"
274 132 />
275 133
276 134 <span
... ...
... ... @@ -8,6 +8,47 @@ import {
8 8 } from '/@/views/visual/packages/index.type';
9 9 import { PublicConfigClass, componentInitConfig } from '/@/views/visual/packages/publicConfig';
10 10 import { ComponentConfigFieldEnum } from '../../../enum';
  11 +import { buildUUID } from '/@/utils/uuid';
  12 +
  13 +export interface SwitchItemType extends Recordable {
  14 + id: string;
  15 + checked: boolean;
  16 + deviceId?: string;
  17 + deviceProfileId?: string;
  18 + attribute?: string;
  19 + deviceName?: string;
  20 + attributeName?: string;
  21 + openCommand?: string;
  22 + closeCommand?: string;
  23 + openService?: string;
  24 + closeService?: string;
  25 +}
  26 +
  27 +export const DEFAULT_VALUE: SwitchItemType[] = [
  28 + {
  29 + deviceName: '光照设备',
  30 + attributeName: '光照',
  31 + icon: 'zongfushe',
  32 + unit: 'kw',
  33 + iconColor: '#367BFF',
  34 + fontColor: '#357CFB',
  35 + checked: false,
  36 + fontSize: 16,
  37 + id: buildUUID(),
  38 + },
  39 + {
  40 + value: 53.7,
  41 + deviceName: '风机设备',
  42 + attributeName: '风机',
  43 + icon: 'guangzhaoqiangdu',
  44 + unit: '℃',
  45 + iconColor: '#FFA000',
  46 + fontColor: '#FFA000',
  47 + checked: true,
  48 + fontSize: 16,
  49 + id: buildUUID(),
  50 + },
  51 +];
11 52
12 53 export const option: PublicPresetOptions = {
13 54 multipleDataSourceComponent: true,
... ...
1 1 <script lang="ts" setup>
2   - import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3   - import { option } from './config';
  2 + import { ComponentMode, ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
  3 + import { DEFAULT_VALUE, option, SwitchItemType } from './config';
4 4 import { SvgIcon } from '/@/components/Icon';
5 5 import { Switch } from 'ant-design-vue';
6   - import { computed, ref } from 'vue';
  6 + import { computed, ref, toRaw } from 'vue';
7 7 import { useComponentScale } from '../../../hook/useComponentScale';
8   - import { useSendCommand } from '../../../hook/useSendCommand';
9 8 import { unref } from 'vue';
10 9 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
11 10 import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
12 11 import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
13 12 import { useReceiveMessage } from '../../../hook/useReceiveMessage';
14 13 import { useReceiveValue } from '../../../hook/useReceiveValue';
15   - import { DataSource } from '/@/views/visual/palette/types';
16   - import { getSendValues, CommandTypeEnumLIst } from '../config';
17 14 import { useModal } from '/@/components/Modal';
18 15 import PasswordModal from '../component/PasswordModal.vue';
  16 + import {
  17 + DoCommandDeliverDataSourceType,
  18 + useCommandDelivery,
  19 + } from '../../../hook/useCommandDelivery';
  20 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
19 21
20 22 const props = defineProps<{
21 23 config: ComponentPropsConfigType<typeof option>;
22 24 }>();
23 25
24   - const svgList = ref<any>([
25   - {
26   - value: 26.2,
27   - deviceName: '光照设备',
28   - attributeName: '光照',
29   - icon: 'zongfushe',
30   - unit: 'kw',
31   - iconColor: '#367BFF',
32   - fontColor: '#357CFB',
33   - checked: false,
34   - id: 0,
35   - },
36   - {
37   - value: 53.7,
38   - deviceName: '风机设备',
39   - attributeName: '风机',
40   - icon: 'guangzhaoqiangdu',
41   - unit: '℃',
42   - iconColor: '#FFA000',
43   - fontColor: '#FFA000',
44   - checked: true,
45   - id: 1,
46   - },
47   - ]);
  26 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
48 27
49 28 const getDesign = computed(() => {
50 29 const { persetOption = {}, option } = props.config;
... ... @@ -64,90 +43,66 @@
64 43 const {
65 44 attribute,
66 45 attributeRename,
67   - attributeName,
68 46 deviceId,
69 47 deviceName,
70 48 deviceRename,
71 49 commandType,
72   - extensionDesc,
73   - codeType,
74   - deviceCode,
75   - customCommand,
  50 + deviceProfileId,
  51 + openCommand,
  52 + closeCommand,
  53 + openService,
  54 + closeService,
76 55 } = item;
  56 +
  57 + const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
  58 +
77 59 return {
78 60 unit: unit ?? persetUnit,
  61 + deviceProfileId,
  62 + deviceId,
79 63 fontColor: fontColor ?? persetFontColor,
80 64 icon: icon ?? persetIcon,
81 65 iconColor: iconColor ?? persetIconColor,
82 66 attribute: attribute,
83   - attributeName: attributeRename || attributeName,
  67 + attributeName: attributeRename || tsl?.functionName || attribute,
84 68 showDeviceName,
85 69 deviceName: deviceRename || deviceName,
86 70 id: deviceId,
87   - extensionDesc: extensionDesc ? JSON.parse(extensionDesc) : {},
88 71 commandType,
89   - codeType,
90   - deviceCode,
91   - customCommand,
92 72 fontSize: fontSize || persetFontSize || 14,
93 73 password: password || persetPassword,
94   - };
  74 + checked: false,
  75 + openCommand,
  76 + closeCommand,
  77 + openService,
  78 + closeService,
  79 + } as SwitchItemType;
95 80 }),
96 81 };
97 82 });
98 83
99   - const { loading, sendCommand } = useSendCommand();
100   - const handleChange = async (index: number, checked: Boolean, item: any) => {
  84 + const handleChange = async (item) => {
101 85 if (item.password) {
102   - openModal(true, { password: item.password, index, checked });
103   - controlList.value[index].checked = !checked;
  86 + openModal(true, { password: item.password });
104 87 return;
105 88 }
106   - const { heightPx, itemHeightRatio, itemWidthRatio, mode, widthPx, dataSource } =
107   - props.config.option;
108   - const data = {
109   - ...dataSource?.[index],
110   - heightPx,
111   - itemHeightRatio,
112   - itemWidthRatio,
113   - mode,
114   - widthPx,
115   - } as DataSource;
116   - const { values, isModbusCommand, sendValue } =
117   - (await getSendValues(data, unref(getDesign).dataSource[index], checked)) || {};
118   -
119   - const flag = await sendCommand(values, isModbusCommand ? sendValue : checked, isModbusCommand);
120   - if (!flag) controlList.value[index].checked = !checked;
  89 + handleSendCommand(item);
121 90 };
122 91
123 92 const { forEachGroupMessage } = useReceiveMessage();
124 93 const { getNumberValue } = useReceiveValue();
125 94
126 95 const controlList = ref(
127   - props.config.option.dataSource
128   - ? unref(getDesign).dataSource.map((item) => {
129   - return { ...item, checked: false };
130   - })
131   - : svgList
  96 + props.config.option.dataSource ? unref(getDesign).dataSource : DEFAULT_VALUE
132 97 );
133   - const handleSendCommand = async (modalData) => {
134   - const { index, checked } = modalData || {};
135   - const { heightPx, itemHeightRatio, itemWidthRatio, mode, widthPx, dataSource } =
136   - props.config.option;
137   - const data = {
138   - ...dataSource?.[index],
139   - heightPx,
140   - itemHeightRatio,
141   - itemWidthRatio,
142   - mode,
143   - widthPx,
144   - } as DataSource;
145   - controlList.value[index].checked = checked;
146   - const { values, isModbusCommand, sendValue } =
147   - (await getSendValues(data, unref(getDesign).dataSource[index], checked)) || {};
148 98
149   - const flag = await sendCommand(values, isModbusCommand ? sendValue : checked, isModbusCommand);
150   - if (!flag) controlList.value[index].checked = !checked;
  99 + const { loading, doCommandDeliver } = useCommandDelivery();
  100 + const handleSendCommand = async (modalData: SwitchItemType) => {
  101 + if (props.config.option.mode === ComponentMode.SELECT_PREVIEW) return;
  102 + await doCommandDeliver(
  103 + toRaw(unref(modalData)) as DoCommandDeliverDataSourceType,
  104 + Number(unref(modalData).checked)
  105 + );
151 106 };
152 107
153 108 const updateFn: MultipleDataFetchUpdateFn = async (message, deviceId, attribute) => {
... ... @@ -169,34 +124,26 @@
169 124 <main class="w-full h-full flex flex-col justify-evenly items-center">
170 125 <DeviceName :config="config" />
171 126 <div
172   - style="width: 86%"
173   - v-for="(item, index) in controlList"
  127 + v-for="item in controlList"
174 128 :key="item.id"
175   - class="flex justify-between items-center"
  129 + class="flex justify-between items-center w-full px-4"
176 130 >
177   - <div class="flex items-center">
178   - <SvgIcon
179   - :name="item.icon!"
180   - prefix="iconfont"
181   - :size="getRatio ? 30 * getRatio : 30"
182   - :style="{ color: item.iconColor }"
183   - />
184   -
185   - <div
186   - class="text-gray-500 truncate ml-6"
187   - :style="{ fontSize: (getRatio ? getRatio * item.fontSize : item.fontSize) + 'px' }"
188   - >{{
189   - `${item.deviceName} - ${
190   - item.commandType ? CommandTypeEnumLIst[item.commandType].name : item.attributeName
191   - }`
192   - }}</div
193   - >
  131 + <SvgIcon
  132 + :name="item.icon!"
  133 + prefix="iconfont"
  134 + :size="getRatio ? 30 * getRatio : 30"
  135 + :style="{ color: item.iconColor }"
  136 + />
  137 + <div
  138 + class="text-gray-500 truncate mx-2"
  139 + :style="{ fontSize: (getRatio ? getRatio * item.fontSize : item.fontSize) + 'px' }"
  140 + >
  141 + {{ `${item.deviceName} - ${item.attributeName}` }}
194 142 </div>
195   -
196 143 <Switch
197 144 v-model:checked="item.checked"
198 145 :loading="loading"
199   - @change="handleChange(index, item.checked, item)"
  146 + @change="handleChange(item)"
200 147 :style="{ transform: `scale(${getRatio || 1})` }"
201 148 />
202 149 </div>
... ...
1   -import { ref, unref } from 'vue';
2   -import { TaskTypeEnum } from '/@/views/task/center/config';
3   -import { genModbusCommand } from '/@/api/task';
4   -import { useMessage } from '/@/hooks/web/useMessage';
5   -import { SingleToHex } from '/@/views/device/list/cpns/tabs/ObjectModelCommandDeliveryModal/config';
6   -import { CommandTypeEnum } from '/@/enums/deviceEnum';
7   -import { TransportTypeEnum } from '/@/enums/deviceEnum';
8   -
9   -const getArray = (values) => {
10   - const str = values.replace(/\s+/g, '');
11   - const array: any = [];
12   -
13   - for (let i = 0; i < str.length; i += 4) {
14   - const chunk = parseInt(str.substring(i, i + 4), 16);
15   - array.push(chunk);
16   - }
17   - return array;
18   -};
19   -
20   -export const getSendValues = async (option, getDesign, checked) => {
21   - const values: any = option;
22   - const isModbusCommand = ref<boolean>(false); //判断是否是TCP设备-> 且设备标识符是modeBUs,切选择是下发命令类型是属性
23   - const { extensionDesc, commandType, codeType, deviceCode, customCommand } = getDesign || {};
24   - const { registerAddress, actionType } = extensionDesc || {};
25   - const { openCommand, closeCommand, transportType } = customCommand || {};
26   - const modBUS = ref<any>({});
27   - const sendValue = ref();
28   -
29   - const { createMessage } = useMessage();
30   - //判断是不是TCP类型设备
31   - if (transportType === TransportTypeEnum.TCP) {
32   - if (
33   - //判断TCP下发类型是否是自定义还是服务
34   - commandType === CommandTypeEnum.CUSTOM.toString() ||
35   - commandType == CommandTypeEnum.SERVICE.toString()
36   - ) {
37   - values.customCommand.command = checked ? openCommand : closeCommand;
38   - values.customCommand.command = values.customCommand.command
39   - .replaceAll(/\s/g, '')
40   - .toUpperCase();
41   - }
42   - if (
43   - //判断命令下发类型是不是属性 且是modbus
44   - commandType === CommandTypeEnum.ATTRIBUTE.toString() &&
45   - codeType === TaskTypeEnum.MODBUS_RTU
46   - ) {
47   - if (!deviceCode) {
48   - createMessage.warning('当前设备没有设置地址码');
49   - return;
50   - }
51   - if (!actionType) {
52   - createMessage.warning('当前物模型扩展描述没有填写');
53   - return;
54   - }
55   - isModbusCommand.value = true;
56   - modBUS.value = {
57   - crc: 'CRC_16_LOWER',
58   - deviceCode: deviceCode,
59   - method: actionType == '16' ? '10' : actionType,
60   - registerAddress,
61   - registerNumber: 1,
62   - registerValues: [Number(checked)],
63   - };
64   -
65   - if (actionType == '16' || actionType == '10') {
66   - const newValue = Number(checked) == 0 ? [0, 0] : getArray(SingleToHex(Number(checked)));
67   - modBUS.value.registerValues = newValue;
68   - modBUS.value.registerNumber = 2;
69   - modBUS.value.method = '10';
70   - }
71   -
72   - sendValue.value = await genModbusCommand(unref(modBUS));
73   - }
74   - }
75   -
76   - return { values, sendValue: unref(sendValue), isModbusCommand: unref(isModbusCommand) };
77   -};
78   -
79   -export const CommandTypeEnumLIst = {
80   - '0': { CUSTOM: 0, name: '自定义' },
81   - '1': { CUSTOM: 1, name: '服务' },
82   - '2': { CUSTOM: 2, name: '属性' },
83   -};
... ... @@ -11,3 +11,7 @@ export const ControlList = [
11 11 LateralNumericalControlConfig,
12 12 SwitchListConfig,
13 13 ];
  14 +
  15 +export enum ControlComponentEnum {
  16 + LATERAL_NUMERICAL_CONTROL = 'LateralNumericalControl',
  17 +}
... ...
... ... @@ -3,11 +3,15 @@
3 3 import { BasicModal, useModalInner } from '/@/components/Modal';
4 4 import { formSchema, getHistorySearchParams, SchemaFiled } from './history.config';
5 5 import { HistoryModalOkEmitParams } from './type';
6   - import { ref, unref } from 'vue';
  6 + import { computed, ref, unref } from 'vue';
7 7 import { getAllDeviceByOrg } from '/@/api/dataBoard';
8 8 import { getDeviceHistoryInfo } from '/@/api/alarm/position';
9 9 import { DataSource } from '/@/views/visual/palette/types';
10   - import { cloneDeep } from 'lodash-es';
  10 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
  11 + import { DataTypeEnum } from '/@/enums/objectModelEnum';
  12 + import { StructJSON } from '/@/api/device/model/modelOfMatterModel';
  13 + import { HistoryData } from '/@/api/alarm/position/model';
  14 + import { useJsonParse } from '/@/hooks/business/useJsonParse';
11 15
12 16 const emit = defineEmits(['register', 'ok']);
13 17
... ... @@ -20,47 +24,44 @@
20 24 });
21 25
22 26 const loading = ref(false);
23   - const getDesign = ref();
  27 + const dataSourceRef = ref<DataSource[]>([]);
24 28
25   - const getTwoMap = async (dataSource) => {
26   - if (dataSource.length < 2) return;
27   - dataSource = dataSource.splice(0, 2);
28   - const deviceRecord = dataSource?.at(0) || ({} as DataSource);
  29 + const getPositionRecord = computed<DataSource>(() => unref(dataSourceRef).at(0) as DataSource);
  30 +
  31 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
  32 +
  33 + const handleSetForm = async () => {
  34 + const deviceRecord = unref(getPositionRecord) || {};
29 35
30 36 if (!deviceRecord.organizationId) return;
31 37 const deviceList = await getAllDeviceByOrg(
32 38 deviceRecord.organizationId,
33 39 deviceRecord.deviceProfileId
34 40 );
  41 +
35 42 const options = deviceList
36 43 .filter((item) => item.tbDeviceId === deviceRecord.deviceId)
37 44 .map((item) => ({ ...item, label: item.alias || item.name, value: item.tbDeviceId }));
38 45
39   - const attKey = dataSource.map((item) => ({
40   - ...item,
41   - label: item.attribute,
42   - value: item.attribute,
43   - }));
  46 + const { latitudeIdentifier, longitudeIdentifier, deviceProfileId } = deviceRecord;
44 47
45   - updateSchemaMap(options, attKey, deviceRecord);
46   - };
  48 + const attKey = [latitudeIdentifier, longitudeIdentifier].map((item) => {
  49 + const [identifier, structIdentifier] = item || [];
  50 + const detail = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, identifier);
47 51
48   - const getOneMap = async (dataSource) => {
49   - const deviceRecord = dataSource?.[0];
50   - if (!deviceRecord.organizationId) return;
51   - const deviceList = await getAllDeviceByOrg(
52   - deviceRecord.organizationId,
53   - deviceRecord.deviceProfileId
54   - );
55   - const options = deviceList
56   - .filter((item) => item.tbDeviceId === deviceRecord.deviceId)
57   - .map((item) => ({ ...item, label: item.alias || item.name, value: item.tbDeviceId }));
  52 + if (detail?.specs?.dataType.type === DataTypeEnum.STRUCT) {
  53 + const structIdentifierDetail = (detail.specs.dataType.specs as StructJSON[])?.find(
  54 + (temp) => temp.identifier === structIdentifier
  55 + );
  56 + return {
  57 + label: `${detail.functionName} / ${structIdentifierDetail?.functionName}`,
  58 + value: identifier,
  59 + };
  60 + }
  61 +
  62 + return { label: detail?.functionName, value: identifier };
  63 + });
58 64
59   - const attKey = dataSource?.map((item) => ({
60   - ...item,
61   - label: item.attributeName,
62   - value: item.attribute,
63   - }));
64 65 updateSchemaMap(options, attKey, deviceRecord);
65 66 };
66 67
... ... @@ -92,81 +93,73 @@
92 93
93 94 const [registerModal, { closeModal }] = useModalInner(async (dataSource: DataSource[]) => {
94 95 try {
95   - getDesign.value = dataSource;
96   - dataSource = cloneDeep(dataSource);
97   - //判断选择是不是结构体
98   - if (dataSource.length == 1 && dataSource?.[0]?.lal) {
99   - getOneMap(dataSource);
100   - return;
101   - }
102   - getTwoMap(dataSource);
  96 + dataSourceRef.value = dataSource;
  97 + handleSetForm();
103 98 } catch (error) {
104 99 throw error;
105 100 }
106 101 });
107 102
108   - const getMultipleDataSource = async (res) => {
  103 + const getPositionDataSource = async (res: HistoryData) => {
  104 + const keys = Object.keys(res);
  105 + const track: Record<'lng' | 'lat', number>[] = [];
  106 +
  107 + const {
  108 + latitudeIdentifier = [],
  109 + longitudeIdentifier = [],
  110 + deviceProfileId,
  111 + } = unref(getPositionRecord);
  112 +
  113 + const [latIdentifier] = latitudeIdentifier || [];
  114 + const [lngIdentifier] = longitudeIdentifier || [];
  115 +
  116 + if (!latIdentifier || !lngIdentifier) return track;
  117 +
  118 + const latDetail = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, latIdentifier);
  119 + const lngDetail = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, lngIdentifier);
  120 +
  121 + if (!latDetail || !lngDetail) return track;
  122 +
109 123 let timespanList = Object.keys(res).reduce((prev, next) => {
110 124 const ts = res[next].map((item) => item.ts);
111 125 return [...prev, ...ts];
112 126 }, [] as number[]);
113 127 timespanList = [...new Set(timespanList)];
114 128
115   - const track: Record<'lng' | 'lat', number>[] = [];
116   - const keys = Object.keys(res);
  129 + for (const position of [longitudeIdentifier, latitudeIdentifier]) {
  130 + const [attribute] = position;
  131 + const detail = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
  132 + if (!detail) continue;
  133 + const { identifier } = detail || {};
  134 +
  135 + if (detail?.specs?.dataType.type === DataTypeEnum.STRUCT) {
  136 + const [, structIdentifier] = position;
  137 + res[identifier].forEach((temp) => {
  138 + const structJSON = useJsonParse(temp.value).value;
  139 + temp.value = structJSON?.[structIdentifier];
  140 + });
  141 + }
  142 + }
117 143
118 144 for (const ts of timespanList) {
119 145 const list: { ts: number; value: number }[] = [];
120 146 for (const key of keys) {
121 147 const record = res[key].find((item) => ts === item.ts);
122   - if (!validEffective(record?.value)) {
123   - continue;
124   - }
125 148 list.push(record as any);
126 149 }
127 150
128 151 if (list.length === 2 && list.every(Boolean)) {
129   - const lng = list.at(0)?.value;
130   - const lat = list.at(1)?.value;
131   - if (lng && lat) track.push({ lng, lat });
  152 + let lng = list.at(0)?.value;
  153 + let lat = list.at(1)?.value;
  154 + if (lng && lat) {
  155 + track.push({ lng: Number(lng), lat: Number(lat) });
  156 + }
132 157 }
133 158 }
134   - return track;
135   - };
136   -
137   - // 单数据源选择结构体
138   - const getSingleDataSource = async (res) => {
139   - const [keys] = Object.keys(res);
140   - const track: Record<'lng' | 'lat', number>[] = [];
141   - const dataSource = res[keys]?.map((item) => {
142   - return {
143   - value: item.value ? JSON.parse(item.value) : {},
144   - };
145   - });
146   - const list: { value: number }[] = [];
147   - dataSource?.forEach((item) => {
148   - if (
149   - //判断结构体得值是不是数字
150   - Object.values(item.value).every((item1) => {
151   - return validEffective(item1 as any);
152   - })
153   - ) {
154   - list.push(item.value);
155   - }
156   - });
157   - const { latitude, longitude } = unref(getDesign)?.[0]; //获取选择的经纬度选项
158 159
159   - list.forEach((item) => {
160   - const lng = item[longitude];
161   - const lat = item[latitude];
162   - if (lng && lat) track.push({ lng, lat });
163   - });
164 160 return track;
165 161 };
166 162
167   - const validEffective = (value = '') => {
168   - return !!(value && !isNaN(value as unknown as number));
169   - };
170 163 const handleOk = async () => {
171 164 try {
172 165 await validate();
... ... @@ -180,9 +173,8 @@
180 173 ...value,
181 174 [SchemaFiled.KEYS]: value[SchemaFiled.KEYS].join(','),
182 175 });
183   - const ifSingle = unref(getDesign)?.length === 1 && unref(getDesign)?.[0]?.lal;
184 176
185   - const track = ifSingle ? await getSingleDataSource(res) : await getMultipleDataSource(res);
  177 + const track = await getPositionDataSource(res);
186 178 emit('ok', { track, value } as HistoryModalOkEmitParams);
187 179 closeModal();
188 180 } catch (error) {
... ...
  1 +import { CategoryEnum } from '../..';
1 2 import { useComponentKeys } from '/@/views/visual/packages/hook/useComponentKeys';
2 3 import { ConfigType, PackagesCategoryEnum } from '/@/views/visual/packages/index.type';
3 4
... ... @@ -6,5 +7,6 @@ const componentKeys = useComponentKeys('MapComponentTrackHistory');
6 7 export const MapComponentTrackHistoryConfig: ConfigType = {
7 8 ...componentKeys,
8 9 title: '历史轨迹',
  10 + category: CategoryEnum.MAP,
9 11 package: PackagesCategoryEnum.MAP,
10 12 };
... ...
... ... @@ -50,6 +50,7 @@
50 50 endTs,
51 51 formatType
52 52 )}`;
  53 +
53 54 genTrackPlaybackAnimation(track as any[]);
54 55 };
55 56
... ...
  1 +import { CategoryEnum } from '../..';
1 2 import { useComponentKeys } from '/@/views/visual/packages/hook/useComponentKeys';
2 3 import { ConfigType, PackagesCategoryEnum } from '/@/views/visual/packages/index.type';
3 4
... ... @@ -6,5 +7,6 @@ const componentKeys = useComponentKeys('MapComponentTrackReal');
6 7 export const MapComponentTrackRealConfig: ConfigType = {
7 8 ...componentKeys,
8 9 title: '实时轨迹',
  10 + category: CategoryEnum.MAP,
9 11 package: PackagesCategoryEnum.MAP,
10 12 };
... ...
... ... @@ -9,6 +9,7 @@
9 9 import { useMapTrackPlayBack } from './useMapTrackPlayback';
10 10 import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
11 11 import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  12 + import get from 'lodash-es/get';
12 13
13 14 const props = defineProps<{
14 15 config: ComponentPropsConfigType<typeof option>;
... ... @@ -24,44 +25,36 @@
24 25 return props.config.option.dataSource?.at(0)?.deviceId;
25 26 });
26 27
27   - const getDesign = computed(() => {
28   - const { option } = props.config;
29   - const { dataSource } = option || {};
30   - return {
31   - dataSource: dataSource,
32   - };
33   - });
34   -
35 28 /**
36 29 * @description 经度key
37 30 */
38 31 const getLngKey = computed(() => {
39   - return props.config.option.dataSource?.at(0)?.attribute || '';
  32 + return props.config.option.dataSource?.at(0)?.longitudeIdentifier || [];
40 33 });
41 34
42 35 /**
43 36 * @description 纬度key
44 37 */
45 38 const getLatKey = computed(() => {
46   - return props.config.option.dataSource?.at(1)?.attribute || '';
  39 + return props.config.option.dataSource?.at(0)?.latitudeIdentifier || [];
47 40 });
48 41
49 42 const validEffective = (value = '') => {
50 43 return !!(value && !isNaN(value as unknown as number));
51 44 };
52 45
53   - const getTwoMap = (message, deviceId) => {
  46 + const handleMessage = (message, deviceId) => {
54 47 if (unref(getDeviceId) !== deviceId) return;
55 48
56 49 const { data = {} } = message;
57 50
58 51 const bindMessage = data[deviceId];
59 52
60   - const lngData = bindMessage[unref(getLngKey)] || [];
  53 + const lngData = get(bindMessage, unref(getLngKey)) || [];
61 54 const [lngLatest] = lngData;
62 55 const [, lng] = lngLatest || [];
63 56
64   - const latData = bindMessage[unref(getLatKey)] || [];
  57 + const latData = get(bindMessage, unref(getLatKey)) || [];
65 58 const [latLatest] = latData;
66 59 const [, lat] = latLatest || [];
67 60
... ... @@ -70,32 +63,8 @@
70 63 }
71 64 };
72 65
73   - const getOneMap = (message, deviceId) => {
74   - if (unref(getDeviceId) !== deviceId) return;
75   -
76   - const { longitude, latitude, attribute } = unref(getDesign)?.dataSource?.[0] || {};
77   - const { data } = message || {};
78   - const bindMessage = data[deviceId];
79   - const [, values] = attribute && bindMessage?.[attribute][0];
80   -
81   - const mapValues = values ? JSON.parse(values) : {};
82   - const lng = longitude && mapValues[longitude];
83   - const lat = latitude && mapValues[latitude];
84   -
85   - if (validEffective(lng) && validEffective(lat)) {
86   - drawLine({ lng: Number(lng), lat: Number(lat) });
87   - }
88   - };
89   -
90 66 const updateFn: MultipleDataFetchUpdateFn = (message, deviceId) => {
91   - const { lal } = unref(getDesign)?.dataSource?.[0] || {};
92   - // 属性选择结构体类型时
93   - if (lal && unref(getDesign)?.dataSource?.length == 1) {
94   - getOneMap(message, deviceId);
95   - return;
96   - }
97   - // 选择两个属性时
98   - getTwoMap(message, deviceId);
  67 + handleMessage(message, deviceId);
99 68 };
100 69
101 70 useMultipleDataFetch(props, updateFn);
... ...
1   -import cloneDeep from 'lodash-es/cloneDeep';
2   -import { SwitchSignalLightConfig } from '.';
3   -import {
4   - ConfigType,
5   - CreateComponentType,
6   - PublicComponentOptions,
7   - PublicPresetOptions,
8   -} from '/@/views/visual/packages/index.type';
9   -import { PublicConfigClass, componentInitConfig } from '/@/views/visual/packages/publicConfig';
10   -import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
11   -
12   -export const option: PublicPresetOptions = {
13   - [ComponentConfigFieldEnum.OPEN_COLOR]: '#30f230',
14   - [ComponentConfigFieldEnum.CLOSE_COLOR]: '#eeeeee',
15   - [ComponentConfigFieldEnum.SHOW_DEVICE_NAME]: true,
16   -};
17   -
18   -export default class Config extends PublicConfigClass implements CreateComponentType {
19   - public key: string = SwitchSignalLightConfig.key;
20   -
21   - public attr = { ...componentInitConfig };
22   -
23   - public componentConfig: ConfigType = cloneDeep(SwitchSignalLightConfig);
24   -
25   - public persetOption = cloneDeep(option);
26   -
27   - public option: PublicComponentOptions;
28   -
29   - constructor(option: PublicComponentOptions) {
30   - super();
31   - this.option = { ...option };
32   - }
33   -}
1   -<script lang="ts" setup>
2   - import { ComponentConfigFieldEnum } from '/@/views/visual/packages/enum';
3   - import { useForm, BasicForm } from '/@/components/Form';
4   - import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5   - import { option } from './config';
6   -
7   - const [register, { getFieldsValue, setFieldsValue, resetFields }] = useForm({
8   - schemas: [
9   - {
10   - field: ComponentConfigFieldEnum.OPEN_COLOR,
11   - label: 'ON颜色',
12   - component: 'ColorPicker',
13   - changeEvent: 'update:value',
14   - componentProps: {
15   - defaultValue: option.openColor,
16   - },
17   - },
18   - {
19   - field: ComponentConfigFieldEnum.CLOSE_COLOR,
20   - label: 'OFF颜色',
21   - component: 'ColorPicker',
22   - changeEvent: 'update:value',
23   - componentProps: {
24   - defaultValue: option.closeColor,
25   - },
26   - },
27   - {
28   - field: ComponentConfigFieldEnum.SHOW_DEVICE_NAME,
29   - label: '显示设备名称',
30   - component: 'Checkbox',
31   - defaultValue: option.showDeviceName,
32   - },
33   - ],
34   - showActionButtonGroup: false,
35   - labelWidth: 120,
36   - baseColProps: {
37   - span: 12,
38   - },
39   - });
40   -
41   - const getFormValues = () => {
42   - return getFieldsValue();
43   - };
44   -
45   - const setFormValues = (data: Recordable) => {
46   - return setFieldsValue(data);
47   - };
48   -
49   - defineExpose({
50   - getFormValues,
51   - setFormValues,
52   - resetFormValues: resetFields,
53   - } as PublicFormInstaceType);
54   -</script>
55   -
56   -<template>
57   - <BasicForm @register="register" />
58   -</template>
1   -<script lang="ts" setup>
2   - import { commonDataSourceSchemas } from '../../../config/common.config';
3   - import { BasicForm, useForm } from '/@/components/Form';
4   - import { PublicFormInstaceType } from '/@/views/visual/dataSourceBindPanel/index.type';
5   -
6   - const [register, { getFieldsValue, setFieldsValue, validate, resetFields }] = useForm({
7   - labelWidth: 0,
8   - showActionButtonGroup: false,
9   - layout: 'horizontal',
10   - labelCol: { span: 0 },
11   - schemas: commonDataSourceSchemas(),
12   - });
13   -
14   - const getFormValues = () => {
15   - return getFieldsValue();
16   - };
17   -
18   - const setFormValues = (record: Recordable) => {
19   - return setFieldsValue(record);
20   - };
21   -
22   - defineExpose({
23   - getFormValues,
24   - setFormValues,
25   - validate,
26   - resetFormValues: resetFields,
27   - } as PublicFormInstaceType);
28   -</script>
29   -
30   -<template>
31   - <BasicForm @register="register" />
32   -</template>
1   -// import { ComponentEnum, ComponentNameEnum } from '../index.type';
2   -import { useComponentKeys } from '/@/views/visual/packages/hook/useComponentKeys';
3   -import { ConfigType, PackagesCategoryEnum } from '/@/views/visual/packages/index.type';
4   -
5   -const componentKeys = useComponentKeys('SwitchSignalLight');
6   -export const SwitchSignalLightConfig: ConfigType = {
7   - ...componentKeys,
8   - // title: ComponentNameEnum.TEXT_COMPONENT_1,
9   - title: '开关量信号灯',
10   - package: PackagesCategoryEnum.TEXT,
11   -};
1   -<script lang="ts" setup>
2   - import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3   - import { option } from './config';
4   - import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
5   - import { ref, onMounted, unref } from 'vue';
6   - import { useIntervalFn } from '@vueuse/core';
7   - import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
8   - import { useReceiveValue } from '../../../hook/useReceiveValue';
9   - import { useDataFetch } from '../../../hook/socket/useSocket';
10   - import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
11   -
12   - const props = defineProps<{
13   - config: ComponentPropsConfigType<typeof option>;
14   - }>();
15   -
16   - const isOpenClose = ref<boolean>(true);
17   -
18   - const randomFn = () => {
19   - useIntervalFn(() => {
20   - isOpenClose.value = !unref(isOpenClose);
21   - }, 2000);
22   - };
23   - const { getNumberValue } = useReceiveValue();
24   - const updateFn: DataFetchUpdateFn = (message, attribute) => {
25   - const { data = {} } = message;
26   - const [latest] = data[attribute] || [];
27   - const [_, value] = latest;
28   - isOpenClose.value = Boolean(getNumberValue(value));
29   - };
30   -
31   - onMounted(() => {
32   - !props.config.option.uuid && randomFn();
33   - });
34   -
35   - useDataFetch(props, updateFn);
36   -
37   - const { getScale } = useComponentScale(props);
38   -</script>
39   -
40   -<template>
41   - <main :style="getScale" class="w-full h-full flex flex-col justify-center items-center">
42   - <DeviceName :config="config" class="text-center" />
43   - <div
44   - style="--open-color: `${getDesign.openColor}`; --close-color: `${getDesign.closeColor}`"
45   - :class="isOpenClose ? 'switch_open' : 'switch_close'"
46   - >
47   - </div>
48   - </main>
49   -</template>
50   -<style lang="less" scoped>
51   - .switch_open {
52   - background: var(--open-color);
53   - box-shadow: var(--open-color) 0 0 10px 3px;
54   - width: 60px;
55   - height: 60px;
56   - border-radius: 50%;
57   - margin: 10px 0;
58   - }
59   -
60   - .switch_close {
61   - background-color: var(--close-color);
62   - box-shadow: none;
63   - width: 42.023px;
64   - height: 42.023px;
65   - border-radius: 50%;
66   - margin: 10px 0;
67   - }
68   -</style>
... ... @@ -2,11 +2,13 @@
2 2 import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4 4 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
5   - import { computed } from 'vue';
  5 + import { computed, unref } from 'vue';
6 6 import { ref } from 'vue';
7 7 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
8 8 import { useDataFetch } from '../../../hook/socket/useSocket';
9 9 import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  10 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
  11 + import { useThingsModelValueTransform } from '/@/views/visual/palette/hooks/useThingsModelValueTransform';
10 12
11 13 const props = defineProps<{
12 14 config: ComponentPropsConfigType<typeof option>;
... ... @@ -14,6 +16,15 @@
14 16
15 17 const currentValue = ref<string | number>(123);
16 18
  19 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
  20 +
  21 + const getThingModelTsl = computed(() => {
  22 + const { option } = props.config;
  23 +
  24 + const { deviceProfileId, attribute } = option;
  25 + return getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
  26 + });
  27 +
17 28 const getDesign = computed(() => {
18 29 const { persetOption = {}, option } = props.config;
19 30
... ... @@ -23,14 +34,14 @@
23 34 valueSize: persetValueSize,
24 35 } = persetOption;
25 36
26   - const { componentInfo, attribute, attributeRename, attributeName } = option;
  37 + const { componentInfo, attribute, attributeRename } = option;
27 38
28 39 const { fontColor, fontSize, valueSize } = componentInfo || {};
29 40 return {
30 41 fontColor: fontColor || persetFontColor,
31 42 fontSize: fontSize || persetFontSize || 14,
32 43 valueSize: valueSize || persetValueSize || 20,
33   - attribute: attributeRename || attributeName || attribute,
  44 + attribute: attributeRename || unref(getThingModelTsl)?.functionName || attribute,
34 45 };
35 46 });
36 47
... ... @@ -39,7 +50,10 @@
39 50 const [latest] = data[attribute] || [];
40 51 if (!latest.length) return;
41 52 const [_, value] = latest;
42   - currentValue.value = value;
  53 + currentValue.value = useThingsModelValueTransform(
  54 + value,
  55 + unref(getThingModelTsl)?.specs?.dataType
  56 + );
43 57 };
44 58
45 59 useDataFetch(props, updateFn);
... ... @@ -52,7 +66,7 @@
52 66 <DeviceName :config="config" />
53 67
54 68 <h1
55   - class="my-4 font-bold !my-2 truncate"
  69 + class="my-4 font-bold !my-2 truncate w-full text-center"
56 70 :style="{
57 71 color: getDesign.fontColor,
58 72 fontSize: (getRatio ? getRatio * getDesign.valueSize : getDesign.valueSize) + 'px',
... ... @@ -63,10 +77,8 @@
63 77 <div
64 78 class="text-gray-500 truncate"
65 79 :style="{ fontSize: (getRatio ? getRatio * getDesign.fontSize : getDesign.fontSize) + 'px' }"
66   - >{{ getDesign.attribute || '温度' }}</div
67 80 >
68   - <!-- <div v-if="config.option.componentInfo.showDeviceName">
69   - {{ config.option.deviceRename || config.option.deviceName }}
70   - </div> -->
  81 + {{ getDesign.attribute || '温度' }}
  82 + </div>
71 83 </main>
72 84 </template>
... ...
... ... @@ -2,12 +2,14 @@
2 2 import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4 4 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
5   - import { computed } from 'vue';
  5 + import { computed, unref } from 'vue';
6 6 import { ref } from 'vue';
7 7 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
8 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
9 9 import { useDataFetch } from '../../../hook/socket/useSocket';
10 10 import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  11 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
  12 + import { useThingsModelValueTransform } from '/@/views/visual/palette/hooks/useThingsModelValueTransform';
11 13
12 14 const props = defineProps<{
13 15 config: ComponentPropsConfigType<typeof option>;
... ... @@ -16,6 +18,15 @@
16 18 const currentValue = ref<string | number>(123);
17 19 const time = ref<Nullable<number>>(null);
18 20
  21 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
  22 +
  23 + const getThingModelTsl = computed(() => {
  24 + const { option } = props.config;
  25 +
  26 + const { deviceProfileId, attribute } = option;
  27 + return getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
  28 + });
  29 +
19 30 const getDesign = computed(() => {
20 31 const { persetOption = {}, option } = props.config;
21 32
... ... @@ -25,7 +36,7 @@
25 36 fontSize: persetFontSize,
26 37 } = persetOption;
27 38
28   - const { componentInfo, attribute, attributeName, attributeRename } = option;
  39 + const { componentInfo, attribute, attributeRename } = option;
29 40
30 41 const { fontColor, valueSize, fontSize } = componentInfo || {};
31 42
... ... @@ -33,7 +44,7 @@
33 44 fontColor: fontColor || persetFontColor,
34 45 valueSize: valueSize || persetValueSize || 20,
35 46 fontSize: fontSize || persetFontSize || 14,
36   - attribute: attributeRename || attributeName || attribute,
  47 + attribute: attributeRename || unref(getThingModelTsl)?.functionName || attribute,
37 48 };
38 49 });
39 50
... ... @@ -42,7 +53,10 @@
42 53 const [info] = data[attribute] || [];
43 54 if (!info.length) return;
44 55 const [timespan, value] = info;
45   - currentValue.value = value;
  56 + currentValue.value = useThingsModelValueTransform(
  57 + value,
  58 + unref(getThingModelTsl)?.specs?.dataType
  59 + );
46 60 time.value = timespan;
47 61 };
48 62
... ... @@ -54,9 +68,9 @@
54 68 <template>
55 69 <main class="w-full h-full flex flex-col justify-center items-center">
56 70 <DeviceName :config="config" />
57   - <div class="flex-1 flex justify-center items-center flex-col" :style="getScale">
  71 + <div class="flex-1 flex justify-center items-center flex-col w-full" :style="getScale">
58 72 <h1
59   - class="my-4 font-bold !my-2 truncate"
  73 + class="my-4 font-bold !my-2 truncate w-full text-center"
60 74 :style="{
61 75 color: getDesign.fontColor,
62 76 fontSize: (getRatio ? getRatio * getDesign.valueSize : getDesign.valueSize) + 'px',
... ...
... ... @@ -2,13 +2,15 @@
2 2 import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4 4 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
5   - import { ref } from 'vue';
  5 + import { ref, unref } from 'vue';
6 6 import { UpdateTime } from '/@/views/visual/commonComponents/UpdateTime';
7 7 import { SvgIcon } from '/@/components/Icon';
8 8 import { computed } from 'vue';
9 9 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
10 10 import { useDataFetch } from '../../../hook/socket/useSocket';
11 11 import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  12 + import { useThingsModelValueTransform } from '/@/views/visual/palette/hooks/useThingsModelValueTransform';
  13 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
12 14
13 15 const props = defineProps<{
14 16 config: ComponentPropsConfigType<typeof option>;
... ... @@ -18,6 +20,15 @@
18 20
19 21 const time = ref<Nullable<number>>(null);
20 22
  23 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
  24 +
  25 + const getThingModelTsl = computed(() => {
  26 + const { option } = props.config;
  27 +
  28 + const { deviceProfileId, attribute } = option;
  29 + return getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
  30 + });
  31 +
21 32 const getDesign = computed(() => {
22 33 const { persetOption = {}, option } = props.config;
23 34
... ... @@ -49,7 +60,10 @@
49 60 const [latest] = data[attribute] || [];
50 61 if (!latest.length) return;
51 62 const [timespan, value] = latest;
52   - currentValue.value = value;
  63 + currentValue.value = useThingsModelValueTransform(
  64 + value,
  65 + unref(getThingModelTsl)?.specs?.dataType
  66 + );
53 67 time.value = timespan;
54 68 };
55 69
... ... @@ -61,7 +75,7 @@
61 75 <template>
62 76 <main :style="getScale" class="w-full h-full flex flex-col justify-center items-center">
63 77 <DeviceName :config="config" />
64   - <div class="flex-1 flex justify-center items-center flex-col">
  78 + <div class="flex-1 flex justify-center items-center flex-col w-full">
65 79 <SvgIcon
66 80 :name="getDesign.icon!"
67 81 prefix="iconfont"
... ... @@ -69,7 +83,7 @@
69 83 :style="{ color: getDesign.iconColor }"
70 84 />
71 85 <h1
72   - class="font-bold m-2 truncate"
  86 + class="font-bold m-2 truncate w-full text-center"
73 87 :style="{
74 88 color: getDesign.fontColor,
75 89 fontSize: (getRatio ? getRatio * getDesign.valueSize : getDesign.valueSize) + 'px',
... ...
... ... @@ -2,12 +2,14 @@
2 2 import { ComponentPropsConfigType } from '/@/views/visual/packages/index.type';
3 3 import { option } from './config';
4 4 import { useComponentScale } from '/@/views/visual/packages/hook/useComponentScale';
5   - import { computed } from 'vue';
  5 + import { computed, unref } from 'vue';
6 6 import { ref } from 'vue';
7 7 import { SvgIcon } from '/@/components/Icon';
8 8 import { DeviceName } from '/@/views/visual/commonComponents/DeviceName';
9 9 import { useDataFetch } from '../../../hook/socket/useSocket';
10 10 import { DataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
  11 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
  12 + import { useThingsModelValueTransform } from '/@/views/visual/palette/hooks/useThingsModelValueTransform';
11 13
12 14 const props = defineProps<{
13 15 config: ComponentPropsConfigType<typeof option>;
... ... @@ -15,6 +17,15 @@
15 17
16 18 const currentValue = ref<string | number>(123);
17 19
  20 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
  21 +
  22 + const getThingModelTsl = computed(() => {
  23 + const { option } = props.config;
  24 +
  25 + const { deviceProfileId, attribute } = option;
  26 + return getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
  27 + });
  28 +
18 29 const getDesign = computed(() => {
19 30 const { persetOption = {}, option } = props.config;
20 31
... ... @@ -27,7 +38,7 @@
27 38 fontSize: persetFontSize,
28 39 } = persetOption;
29 40
30   - const { componentInfo, attribute, attributeRename, attributeName } = option;
  41 + const { componentInfo, attribute, attributeRename } = option;
31 42
32 43 const { icon, iconColor, fontColor, unit, valueSize, fontSize } = componentInfo || {};
33 44 return {
... ... @@ -35,7 +46,7 @@
35 46 unit: unit ?? perseUnit,
36 47 icon: icon || persetIcon,
37 48 fontColor: fontColor || persetFontColor,
38   - attribute: attributeRename || attributeName || attribute,
  49 + attribute: attributeRename || unref(getThingModelTsl)?.functionName || attribute,
39 50 valueSize: valueSize || persetValueSize || 20,
40 51 fontSize: fontSize || persetFontSize || 14,
41 52 };
... ... @@ -46,7 +57,10 @@
46 57 const [info] = data[attribute] || [];
47 58 if (!info.length) return;
48 59 const [_, value] = info;
49   - currentValue.value = value;
  60 + currentValue.value = useThingsModelValueTransform(
  61 + value,
  62 + unref(getThingModelTsl)?.specs?.dataType
  63 + );
50 64 };
51 65
52 66 useDataFetch(props, updateFn);
... ... @@ -57,7 +71,7 @@
57 71 <template>
58 72 <main class="w-full h-full flex flex-col justify-center items-center">
59 73 <DeviceName :config="config" />
60   - <div :style="getScale" class="flex-1 flex justify-center items-center flex-col">
  74 + <div :style="getScale" class="flex-1 flex justify-center items-center flex-col w-full">
61 75 <SvgIcon
62 76 :name="getDesign.icon!"
63 77 prefix="iconfont"
... ... @@ -65,7 +79,7 @@
65 79 :style="{ color: getDesign.iconColor }"
66 80 />
67 81 <h1
68   - class="my-4 font-bold !my-2 truncate"
  82 + class="my-4 font-bold !my-2 truncate w-full text-center"
69 83 :style="{
70 84 color: getDesign.fontColor,
71 85 fontSize: (getRatio ? getRatio * getDesign.valueSize : getDesign.valueSize) + 'px',
... ...
... ... @@ -10,6 +10,7 @@
10 10 import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
11 11 import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
12 12 import { useComponentScale } from '../../../hook/useComponentScale';
  13 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
13 14
14 15 const props = defineProps<{
15 16 config: ComponentPropsConfigType<typeof option>;
... ... @@ -56,6 +57,8 @@
56 57 },
57 58 ];
58 59
  60 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
  61 +
59 62 const getDesign = computed(() => {
60 63 const { persetOption = {}, option } = props.config;
61 64 const { dataSource = [] } = option || {};
... ... @@ -71,15 +74,17 @@
71 74 dataSource: dataSource.map((item) => {
72 75 const { unit, fontColor, backgroundColor, maxNumber, valueSize, fontSize } =
73 76 item.componentInfo || {};
74   - const { attribute, attributeRename, deviceName, deviceRename, deviceId, attributeName } =
  77 + const { attribute, attributeRename, deviceProfileId, deviceName, deviceRename, deviceId } =
75 78 item;
  79 +
  80 + const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
76 81 return {
77 82 unit: unit ?? presetUnit,
78 83 fontColor: fontColor ?? presetFontColor,
79 84 backgroundColor: backgroundColor ?? persetBackgroundColor,
80 85 deviceName: deviceRename || deviceName,
81 86 attribute,
82   - attributeName: attributeRename || attributeName,
  87 + attributeName: attributeRename || tsl?.functionName,
83 88 maxNumber: maxNumber || persetMaxNumber,
84 89 id: deviceId,
85 90 valueSize: valueSize || persetValueSize || 20,
... ...
... ... @@ -10,6 +10,8 @@
10 10 import { useMultipleDataFetch } from '../../../hook/socket/useSocket';
11 11 import { MultipleDataFetchUpdateFn } from '../../../hook/socket/useSocket.type';
12 12 import { useComponentScale } from '../../../hook/useComponentScale';
  13 + import { useDeviceProfileQueryContext } from '/@/views/visual/palette/hooks/useDeviceProfileQueryContext';
  14 + import { buildUUID } from '/@/utils/uuid';
13 15
14 16 const props = defineProps<{
15 17 config: ComponentPropsConfigType<typeof option>;
... ... @@ -17,6 +19,8 @@
17 19
18 20 const time = ref<Nullable<number>>(null);
19 21
  22 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
  23 +
20 24 const getDesign = computed(() => {
21 25 const { persetOption = {}, option } = props.config;
22 26 const { dataSource = [] } = option || {};
... ... @@ -28,12 +32,13 @@
28 32 valueSize: persetValueSize,
29 33 fontSize: persetFontSize,
30 34 } = persetOption || {};
  35 +
31 36 return {
32 37 dataSource: dataSource.map((item) => {
33 38 const { fontColor, icon, iconColor, unit, valueSize, fontSize } = item.componentInfo;
34   - const { attribute, attributeRename, deviceName, deviceRename, deviceId, attributeName } =
  39 + const { attribute, attributeRename, deviceName, deviceRename, deviceId, deviceProfileId } =
35 40 item;
36   -
  41 + const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
37 42 return {
38 43 unit: unit ?? persetUnit,
39 44 fontColor: fontColor ?? persetFontColor,
... ... @@ -41,7 +46,7 @@
41 46 iconColor: iconColor ?? persetIconColor,
42 47 attribute,
43 48 deviceName: deviceRename || deviceName,
44   - attributeName: attributeRename || attributeName,
  49 + attributeName: attributeRename || tsl?.functionName,
45 50 id: deviceId,
46 51 valueSize: valueSize || persetValueSize || 20,
47 52 fontSize: fontSize || persetFontSize || 14,
... ... @@ -49,8 +54,10 @@
49 54 }),
50 55 };
51 56 });
52   - const defaultSvgList = ref<any>([
  57 +
  58 + const defaultSvgList = ref([
53 59 {
  60 + id: buildUUID(),
54 61 value: 26.2,
55 62 deviceName: '温湿度',
56 63 attributeName: '温度',
... ... @@ -58,8 +65,11 @@
58 65 unit: '℃',
59 66 iconColor: '#367BFF',
60 67 fontColor: '#357CFB',
  68 + fontSize: 16,
  69 + valueSize: 16,
61 70 },
62 71 {
  72 + id: buildUUID(),
63 73 value: 53.7,
64 74 deviceName: '温湿度',
65 75 attributeName: '湿度',
... ... @@ -67,6 +77,8 @@
67 77 unit: '℃',
68 78 iconColor: '#FFA000',
69 79 fontColor: '#FFA000',
  80 + fontSize: 16,
  81 + valueSize: 16,
70 82 },
71 83 ]);
72 84
... ... @@ -108,7 +120,6 @@
108 120 :size="getRatio ? 30 * getRatio : 30"
109 121 :style="{ color: item.iconColor }"
110 122 />
111   -
112 123 <div
113 124 class="text-gray-500 ml-6"
114 125 :style="{ fontSize: (getRatio ? getRatio * item.fontSize : item.fontSize) + 'px' }"
... ... @@ -122,10 +133,9 @@
122 133 fontSize: (getRatio ? getRatio * item.valueSize : item.valueSize) + 'px',
123 134 }"
124 135 >
125   - <span> {{ item.value || 0 }}</span>
  136 + <span> {{ (item as any).value || 0 }}</span>
126 137 <span>{{ item.unit }} </span>
127 138 </span>
128 139 </div>
129   - <!-- <UpdateTime :time="time" /> -->
130 140 </main>
131 141 </template>
... ...
  1 +export enum CategoryEnum {
  2 + MAP = 'MAP',
  3 +}
... ...
... ... @@ -3,21 +3,25 @@ import { useSelectWidgetKeys, useSelectWidgetMode } from '../../dataSourceBindPa
3 3 import { PackagesCategoryEnum } from '../index.type';
4 4 import { getDeviceProfile } from '/@/api/alarm/position';
5 5 import { getDeviceAttributes, getMeetTheConditionsDevice } from '/@/api/dataBoard';
6   -import { DeviceAttributeParams, MasterDeviceList } from '/@/api/dataBoard/model';
7   -import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
8   -import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
  6 +import { DeviceAttributeParams } from '/@/api/dataBoard/model';
9 7 import { getModelServices } from '/@/api/device/modelOfMatter';
10 8 import { findDictItemByCode } from '/@/api/system/dict';
11   -import { FormSchema, useComponentRegister } from '/@/components/Form';
  9 +import { ApiCascader, FormSchema, useComponentRegister } from '/@/components/Form';
12 10 import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
13 11 import { DataActionModeEnum } from '/@/enums/toolEnum';
14 12 import { TaskTypeEnum } from '/@/views/task/center/config';
15 13 import { createPickerSearch } from '/@/utils/pickerSearch';
16 14 import { DataTypeEnum } from '/@/enums/objectModelEnum';
17   -import { CommandTypeEnum } from '/@/enums/deviceEnum';
  15 +import { CommandTypeEnum, CommandTypeNameEnum } from '/@/enums/deviceEnum';
18 16 import { TransportTypeEnum } from '/@/enums/deviceEnum';
  17 +import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
  18 +import { ControlComponentEnum } from '../components/Control';
  19 +import { isNullOrUnDef } from '/@/utils/is';
  20 +import { validateTCPCustomCommand } from '/@/components/Form/src/components/ThingsModelForm';
  21 +import { CategoryEnum } from '../components';
19 22
20 23 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
  24 +useComponentRegister('ApiCascader', ApiCascader);
21 25
22 26 export interface CommonDataSourceBindValueType extends Record<DataSourceField, string> {
23 27 customCommand?: {
... ... @@ -31,30 +35,51 @@ export interface CommonDataSourceBindValueType extends Record<DataSourceField, s
31 35 }
32 36
33 37 export enum DataSourceField {
34   - IS_GATEWAY_DEVICE = 'gatewayDevice',
  38 + // IS_GATEWAY_DEVICE = 'gatewayDevice',
35 39 DEVICE_TYPE = 'deviceType',
36 40 TRANSPORT_TYPE = 'transportType',
37 41 ORIGINATION_ID = 'organizationId',
38 42 DEVICE_ID = 'deviceId',
39   - DEVICE_CODE = 'deviceCode', //设备地址码
  43 + // DEVICE_CODE = 'deviceCode', //设备地址码
40 44 DEVICE_PROFILE_ID = 'deviceProfileId',
41 45 ATTRIBUTE = 'attribute',
42   - ATTRIBUTE_NAME = 'attributeName',
  46 + // ATTRIBUTE_NAME = 'attributeName',
43 47 ATTRIBUTE_RENAME = 'attributeRename',
44 48 DEVICE_NAME = 'deviceName',
45 49 DEVICE_RENAME = 'deviceRename',
46   - LONGITUDE_ATTRIBUTE = 'longitudeAttribute',
47   - LATITUDE_ATTRIBUTE = 'latitudeAttribute',
  50 +
48 51 CODE_TYPE = 'codeType',
49   - COMMAND = 'command',
  52 + // COMMAND = 'command',
  53 +
50 54 OPEN_COMMAND = 'openCommand',
51 55 CLOSE_COMMAND = 'closeCommand',
  56 +
52 57 COMMAND_TYPE = 'commandType',
53   - SERVICE = 'service',
  58 +
  59 + // SERVICE = 'service',
  60 +
  61 + /**
  62 + * @description 关服务标识符
  63 + */
54 64 OPEN_SERVICE = 'openService',
  65 +
  66 + /**
  67 + * @description 开服务标识符
  68 + */
55 69 CLOSE_SERVICE = 'closeService',
56   - EXTENSION_DESC = 'extensionDesc',
57   - CALL_TYPE = 'callType',
  70 +
  71 + /**
  72 + * @description 经度标识符
  73 + */
  74 + LONGITUDE_IDENTIFIER = 'longitudeIdentifier',
  75 +
  76 + /**
  77 + * @description 纬度标识符号
  78 + */
  79 + LATITUDE_IDENTIFIER = 'latitudeIdentifier',
  80 +
  81 + // EXTENSION_DESC = 'extensionDesc',
  82 + // CALL_TYPE = 'callType',
58 83 }
59 84
60 85 const isTcpProfile = (transportType: string) => transportType === TransportTypeEnum.TCP;
... ... @@ -69,15 +94,6 @@ const isBooleanComponent = (componentKeys: { categoryKey?: string; componentKey?
69 94 );
70 95 };
71 96
72   -const getDeviceService = async (deviceProfileId: string) => {
73   - try {
74   - const data = await getModelServices({ deviceProfileId });
75   - if (data)
76   - return data.map((item) => ({ ...item, label: item.functionName, value: item.identifier }));
77   - } catch (error) {}
78   - return [];
79   -};
80   -
81 97 const getDeviceAttribute = async (params: DeviceAttributeParams) => {
82 98 try {
83 99 const data = await getDeviceAttributes(params);
... ... @@ -94,24 +110,6 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
94 110
95 111 return [
96 112 {
97   - field: DataSourceField.IS_GATEWAY_DEVICE,
98   - component: 'Switch',
99   - label: '是否是网关设备',
100   - show: false,
101   - },
102   - {
103   - field: DataSourceField.DEVICE_NAME,
104   - component: 'Input',
105   - label: '设备名',
106   - show: false,
107   - },
108   - {
109   - field: DataSourceField.TRANSPORT_TYPE,
110   - component: 'Input',
111   - label: '设备配置类型',
112   - show: false,
113   - },
114   - {
115 113 field: DataSourceField.DEVICE_TYPE,
116 114 component: 'ApiSelect',
117 115 label: '设备类型',
... ... @@ -127,13 +125,11 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
127 125 valueField: 'itemValue',
128 126 labelField: 'itemText',
129 127 placeholder: '请选择设备类型',
130   - onChange: (value: DeviceTypeEnum) => {
  128 + onChange: () => {
131 129 setFieldsValue({
132   - [DataSourceField.IS_GATEWAY_DEVICE]: value === DeviceTypeEnum.GATEWAY,
133 130 [DataSourceField.DEVICE_PROFILE_ID]: null,
134 131 [DataSourceField.DEVICE_ID]: null,
135 132 [DataSourceField.ATTRIBUTE]: null,
136   - [DataSourceField.ATTRIBUTE_NAME]: null,
137 133 [DataSourceField.TRANSPORT_TYPE]: null,
138 134 });
139 135 },
... ... @@ -142,6 +138,12 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
142 138 },
143 139 },
144 140 {
  141 + field: DataSourceField.TRANSPORT_TYPE,
  142 + label: '传输协议',
  143 + component: 'Input',
  144 + ifShow: false,
  145 + },
  146 + {
145 147 field: DataSourceField.DEVICE_PROFILE_ID,
146 148 component: 'ApiSelect',
147 149 label: '产品',
... ... @@ -164,12 +166,11 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
164 166 labelField: 'name',
165 167 valueField: 'id',
166 168 placeholder: '请选择产品',
167   - onChange: (_, option = {} as Record<'transportType', string>) => {
  169 + onChange: (_, option: Record<'transportType', string>) => {
168 170 setFieldsValue({
169 171 [DataSourceField.DEVICE_ID]: null,
170 172 [DataSourceField.ATTRIBUTE]: null,
171   - [DataSourceField.ATTRIBUTE_NAME]: null,
172   - [DataSourceField.TRANSPORT_TYPE]: option[DataSourceField.TRANSPORT_TYPE],
  173 + [DataSourceField.TRANSPORT_TYPE]: option?.[DataSourceField.TRANSPORT_TYPE],
173 174 [DataSourceField.COMMAND_TYPE]: null,
174 175 });
175 176 },
... ... @@ -201,10 +202,10 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
201 202 },
202 203 },
203 204 {
204   - field: DataSourceField.DEVICE_PROFILE_ID,
  205 + field: DataSourceField.DEVICE_NAME,
205 206 component: 'Input',
206   - label: '',
207   - show: false,
  207 + label: '设备名称',
  208 + ifShow: false,
208 209 },
209 210 {
210 211 field: DataSourceField.DEVICE_ID,
... ... @@ -238,12 +239,11 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
238 239 }
239 240 return [];
240 241 },
241   - onChange(_value, record: MasterDeviceList) {
  242 + onChange(_value, options: Record<'label' | 'codeType', string>) {
242 243 setFieldsValue({
243   - [DataSourceField.DEVICE_NAME]: record?.label,
244   - [DataSourceField.CODE_TYPE]: record?.codeType,
245   - [DataSourceField.DEVICE_CODE]: record?.code,
246 244 [DataSourceField.COMMAND_TYPE]: null,
  245 + [DataSourceField.DEVICE_NAME]: options?.label,
  246 + [DataSourceField.CODE_TYPE]: options?.codeType,
247 247 });
248 248 },
249 249 placeholder: '请选择设备',
... ... @@ -253,91 +253,9 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
253 253 },
254 254 {
255 255 field: DataSourceField.CODE_TYPE,
  256 + label: '设备标识符类型',
256 257 component: 'Input',
257   - label: '设备标识符',
258   - show: false,
259   - },
260   -
261   - {
262   - field: DataSourceField.DEVICE_CODE,
263   - component: 'Input',
264   - show: false,
265   - label: '设备地址码',
266   - },
267   -
268   - {
269   - field: DataSourceField.ATTRIBUTE_NAME,
270   - component: 'Input',
271   - label: '属性名',
272   - show: false,
273   - },
274   - {
275   - field: DataSourceField.COMMAND_TYPE,
276   - component: 'ApiSelect',
277   - label: '命令类型',
278   - defaultValue: CommandTypeEnum.CUSTOM.toString(),
279   - rules: [{ required: true, message: '请选择命令类型' }],
280   - colProps: { span: 8 },
281   - ifShow: ({ model }) => {
282   - return isControlComponent(category!) && isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]);
283   - },
284   - componentProps: ({ formActionType, formModel }) => {
285   - const { setFieldsValue } = formActionType;
286   - const { codeType, deviceType, transportType } = formModel || {};
287   - return {
288   - // api: findDictItemByCode,
289   - api: async (params: Recordable) => {
290   - try {
291   - const record = await findDictItemByCode(params);
292   - if (unref(selectWidgetKeys).componentKey == 'LateralNumericalControl') {
293   - return record.filter(
294   - (item) => item.itemValue == CommandTypeEnum.ATTRIBUTE.toString()
295   - );
296   - }
297   - // TCP网关子 --> 不能要服务命令类型
298   - if (deviceType == 'SENSOR' && transportType == 'TCP') {
299   - return codeType == 'MODBUS_RTU'
300   - ? record.filter((item) => item.itemValue == CommandTypeEnum.ATTRIBUTE.toString())
301   - : codeType == 'CUSTOM'
302   - ? record.filter((item) => item.itemValue == CommandTypeEnum.CUSTOM.toString())
303   - : [];
304   - }
305   -
306   - if (codeType == TaskTypeEnum.MODBUS_RTU) {
307   - // setFieldsValue({ [DataSourceField.COMMAND_TYPE]: undefined });
308   - return record.filter(
309   - (item) => item.itemValue !== CommandTypeEnum.CUSTOM.toString()
310   - );
311   - } else {
312   - return record.filter(
313   - (item) => item.itemValue !== CommandTypeEnum.ATTRIBUTE.toString()
314   - );
315   - }
316   - } catch (error) {
317   - return [];
318   - }
319   - },
320   - params: {
321   - dictCode: 'custom_define',
322   - },
323   - labelField: 'itemText',
324   - valueField: 'itemValue',
325   - placeholder: '请选择命令类型',
326   - getPopupContainer: () => document.body,
327   - onChange() {
328   - setFieldsValue({
329   - [DataSourceField.COMMAND]: null,
330   - [DataSourceField.SERVICE]: null,
331   - [DataSourceField.OPEN_COMMAND]: null,
332   - [DataSourceField.CLOSE_COMMAND]: null,
333   - [DataSourceField.OPEN_SERVICE]: null,
334   - [DataSourceField.CLOSE_SERVICE]: null,
335   - [DataSourceField.CALL_TYPE]: null,
336   - [DataSourceField.ATTRIBUTE]: null,
337   - });
338   - },
339   - };
340   - },
  258 + ifShow: false,
341 259 },
342 260 {
343 261 field: DataSourceField.ATTRIBUTE,
... ... @@ -345,9 +263,7 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
345 263 label: '属性',
346 264 colProps: { span: 8 },
347 265 rules: [{ required: true, message: '请选择属性' }],
348   - ifShow: ({ model }) =>
349   - !(isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) && isControlComponent(category!)) ||
350   - model[DataSourceField.COMMAND_TYPE] == 2,
  266 + ifShow: () => category !== CategoryEnum.MAP,
351 267 componentProps({ formModel, formActionType }) {
352 268 const { setFieldsValue } = formActionType;
353 269 const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
... ... @@ -359,20 +275,25 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
359 275 deviceProfileId,
360 276 dataType:
361 277 (isControlComponent(category!) &&
362   - unref(selectWidgetKeys).componentKey !== 'LateralNumericalControl') ||
  278 + unref(selectWidgetKeys).componentKey !==
  279 + ControlComponentEnum.LATERAL_NUMERICAL_CONTROL) ||
363 280 isBooleanComponent(unref(selectWidgetKeys))
364 281 ? DataTypeEnum.BOOL
365 282 : undefined,
366 283 });
367 284
368 285 // 选择控制组件4的时候只能选择属性且是(int double类型)
369   - if (unref(selectWidgetKeys).componentKey == 'LateralNumericalControl') {
  286 + if (
  287 + unref(selectWidgetKeys).componentKey ===
  288 + ControlComponentEnum.LATERAL_NUMERICAL_CONTROL
  289 + ) {
370 290 setFieldsValue({
371 291 [DataSourceField.COMMAND_TYPE]: CommandTypeEnum.ATTRIBUTE.toString(),
372 292 });
373 293 return option.filter(
374 294 (item) =>
375   - item.detail.dataType.type == 'INT' || item.detail.dataType.type == 'DOUBLE'
  295 + item.detail.dataType.type === DataTypeEnum.NUMBER_INT ||
  296 + item.detail.dataType.type == DataTypeEnum.NUMBER_DOUBLE
376 297 );
377 298 }
378 299 return option;
... ... @@ -381,76 +302,133 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
381 302 return [];
382 303 },
383 304 placeholder: '请选择属性',
384   - onChange(value: string, option: Record<'label' | 'value' | any, string>) {
385   - const { detail }: any = option || {};
386   - setFieldsValue({
387   - [DataSourceField.ATTRIBUTE_NAME]: value ? option.label : null,
388   - [DataSourceField.EXTENSION_DESC]: value ? JSON.stringify(option.extensionDesc) : '',
389   - // 地图组件结构体
390   - lal:
391   - category === 'MAP' && value && detail?.dataType.type === 'STRUCT'
392   - ? JSON.stringify(detail?.dataType.specs)
393   - : null,
394   - latitude: null,
395   - longitude: null,
396   - });
397   - },
398 305 ...createPickerSearch(),
399 306 };
400 307 },
401 308 },
402 309 {
403   - field: 'lal',
404   - label: '经纬度存储的值',
405   - component: 'Input',
406   - show: false,
407   - },
408   - {
409   - field: 'longitude',
  310 + field: DataSourceField.LONGITUDE_IDENTIFIER,
410 311 label: '经度',
  312 + component: 'ApiCascader',
  313 + ifShow: ({}) => category === CategoryEnum.MAP,
411 314 colProps: { span: 8 },
412   - component: 'Select',
413   - required: true,
414   - ifShow: ({ model }) => category === 'MAP' && model.lal,
415   - componentProps({ formModel }) {
416   - const { lal } = formModel || {};
  315 + changeEvent: 'update:value',
  316 + valueField: 'value',
  317 + componentProps: ({ formModel }) => {
  318 + const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
417 319 return {
  320 + api: async () => {
  321 + try {
  322 + if (deviceProfileId) {
  323 + const options = await getDeviceAttributes({
  324 + deviceProfileId,
  325 + });
  326 +
  327 + const _options = options.map((item) => {
  328 + item = Object.assign(item, { functionName: item.name });
  329 + if (item.detail.dataType.type === DataTypeEnum.STRUCT) {
  330 + return Object.assign(item, { specs: item.detail.dataType.specs });
  331 + }
  332 + return item;
  333 + });
  334 + return _options;
  335 + }
  336 + } catch (error) {}
  337 + return [];
  338 + },
418 339 placeholder: '请选择经度',
419   - options: lal
420   - ? JSON.parse(lal)?.map((item) => ({ label: item.functionName, value: item.identifier }))
421   - : [],
  340 + getPopupContainer: () => document.body,
  341 + fieldNames: { label: 'functionName', value: 'identifier', children: 'specs' },
422 342 };
423 343 },
424 344 },
425 345 {
426   - field: 'latitude',
  346 + field: DataSourceField.LATITUDE_IDENTIFIER,
427 347 label: '纬度',
  348 + component: 'ApiCascader',
  349 + ifShow: ({}) => category === CategoryEnum.MAP,
428 350 colProps: { span: 8 },
429   - required: true,
430   - component: 'Select',
431   - ifShow: ({ model }) => category === 'MAP' && model.lal,
432   - componentProps({ formModel }) {
433   - const { lal } = formModel || {};
  351 + changeEvent: 'update:value',
  352 + valueField: 'value',
  353 + componentProps: ({ formModel }) => {
  354 + const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
  355 +
434 356 return {
  357 + api: async () => {
  358 + try {
  359 + if (deviceProfileId) {
  360 + const options = await getDeviceAttributes({
  361 + deviceProfileId,
  362 + });
  363 +
  364 + const _options = options.map((item) => {
  365 + item = Object.assign(item, { functionName: item.name });
  366 +
  367 + if (item.detail.dataType.type === DataTypeEnum.STRUCT) {
  368 + return Object.assign(item, { specs: item.detail.dataType.specs });
  369 + }
  370 + return item;
  371 + });
  372 +
  373 + return _options;
  374 + }
  375 + } catch (error) {}
  376 + return [];
  377 + },
435 378 placeholder: '请选择纬度',
436   - options: lal
437   - ? JSON.parse(lal)?.map((item) => ({ label: item.functionName, value: item.identifier }))
438   - : [],
  379 + getPopupContainer: () => document.body,
  380 + fieldNames: { label: 'functionName', value: 'identifier', children: 'specs' },
439 381 };
440 382 },
441 383 },
442 384 {
443   - field: DataSourceField.EXTENSION_DESC,
444   - component: 'Input',
445   - show: false,
446   - label: '扩展描述符',
447   - },
  385 + field: DataSourceField.COMMAND_TYPE,
  386 + component: 'Select',
  387 + label: '命令类型',
  388 + defaultValue: CommandTypeEnum.CUSTOM,
  389 + rules: [
  390 + {
  391 + required: true,
  392 + validator(_, value: number | string) {
  393 + if (isNullOrUnDef(value)) return Promise.reject('请选择命令类型');
  394 + return Promise.resolve();
  395 + },
  396 + },
  397 + ],
  398 + colProps: { span: 8 },
  399 + ifShow: ({ model }) => {
  400 + return (
  401 + isControlComponent(category!) &&
  402 + unref(selectWidgetKeys).componentKey !== ControlComponentEnum.LATERAL_NUMERICAL_CONTROL &&
  403 + isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]) &&
  404 + model[DataSourceField.CODE_TYPE] === TaskTypeEnum.CUSTOM
  405 + );
  406 + },
  407 + componentProps: ({ formActionType, formModel }) => {
  408 + const { setFieldsValue } = formActionType;
  409 + const deviceType = formModel[DataSourceField.DEVICE_TYPE];
  410 + const options: Record<'label' | 'value', string | number>[] = [
  411 + { label: CommandTypeNameEnum.CUSTOM, value: CommandTypeEnum.CUSTOM },
  412 + ];
448 413
449   - {
450   - field: DataSourceField.CALL_TYPE,
451   - component: 'Input',
452   - ifShow: false,
453   - label: 'callType',
  414 + // 网关子设备无服务
  415 + if (deviceType !== DeviceTypeEnum.SENSOR)
  416 + options.push({ label: CommandTypeNameEnum.SERVICE, value: CommandTypeEnum.SERVICE });
  417 +
  418 + return {
  419 + options,
  420 + placeholder: '请选择命令类型',
  421 + getPopupContainer: () => document.body,
  422 + onChange() {
  423 + setFieldsValue({
  424 + [DataSourceField.OPEN_COMMAND]: null,
  425 + [DataSourceField.CLOSE_COMMAND]: null,
  426 + [DataSourceField.OPEN_SERVICE]: null,
  427 + [DataSourceField.CLOSE_SERVICE]: null,
  428 + });
  429 + },
  430 + };
  431 + },
454 432 },
455 433 {
456 434 field: DataSourceField.OPEN_SERVICE,
... ... @@ -460,40 +438,19 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
460 438 rules: [{ required: true, message: '请选择开服务' }],
461 439 ifShow: ({ model }) =>
462 440 isControlComponent(category!) &&
463   - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE.toString() &&
  441 + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE &&
464 442 isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),
465   - componentProps({ formModel, formActionType }) {
466   - const { setFieldsValue } = formActionType;
  443 + componentProps({ formModel }) {
467 444 const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
468   - const transportType = formModel[DataSourceField.TRANSPORT_TYPE];
469   - if (isUpdate && ![deviceProfileId, transportType].every(Boolean))
470   - return { placeholder: '请选择开服务', getPopupContainer: () => document.body };
471 445 return {
472   - api: async () => {
473   - try {
474   - if (deviceProfileId) {
475   - const services = await getDeviceService(deviceProfileId);
476   - const value = formModel[DataSourceField.SERVICE];
477   - if (value) {
478   - const selected = services.find((item) => item.value === value);
479   - selected && setFieldsValue({ [DataSourceField.CALL_TYPE]: selected.callType });
480   - }
481   - return services;
482   - }
483   - } catch (error) {}
484   - return [];
  446 + api: getModelServices,
  447 + params: {
  448 + deviceProfileId,
485 449 },
486 450 placeholder: '请选择开服务',
  451 + labelField: 'functionName',
  452 + valueField: 'identifier',
487 453 getPopupContainer: () => document.body,
488   - onChange(value: string, options: ModelOfMatterParams) {
489   - const command = value
490   - ? (options.functionJson.inputData || [])[0]?.serviceCommand
491   - : null;
492   - setFieldsValue({
493   - [DataSourceField.OPEN_COMMAND]: command,
494   - [DataSourceField.CALL_TYPE]: value ? options.callType : null,
495   - });
496   - },
497 454 };
498 455 },
499 456 },
... ... @@ -505,40 +462,19 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
505 462 rules: [{ required: true, message: '请选择关服务' }],
506 463 ifShow: ({ model }) =>
507 464 isControlComponent(category!) &&
508   - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE.toString() &&
  465 + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.SERVICE &&
509 466 isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),
510   - componentProps({ formModel, formActionType }) {
511   - const { setFieldsValue } = formActionType;
  467 + componentProps({ formModel }) {
512 468 const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
513   - const transportType = formModel[DataSourceField.TRANSPORT_TYPE];
514   - if (isUpdate && ![deviceProfileId, transportType].every(Boolean))
515   - return { placeholder: '请选择关服务', getPopupContainer: () => document.body };
516 469 return {
517   - api: async () => {
518   - try {
519   - if (deviceProfileId) {
520   - const services = await getDeviceService(deviceProfileId);
521   - const value = formModel[DataSourceField.SERVICE];
522   - if (value) {
523   - const selected = services.find((item) => item.value === value);
524   - selected && setFieldsValue({ [DataSourceField.CALL_TYPE]: selected.callType });
525   - }
526   - return services;
527   - }
528   - } catch (error) {}
529   - return [];
  470 + api: getModelServices,
  471 + params: {
  472 + deviceProfileId,
530 473 },
531 474 placeholder: '请选择关服务',
  475 + labelField: 'functionName',
  476 + valueField: 'identifier',
532 477 getPopupContainer: () => document.body,
533   - onChange(value: string, options: ModelOfMatterParams) {
534   - const command = value
535   - ? (options.functionJson.inputData || [])[0]?.serviceCommand
536   - : null;
537   - setFieldsValue({
538   - [DataSourceField.CLOSE_COMMAND]: command,
539   - [DataSourceField.CALL_TYPE]: value ? options.callType : null,
540   - });
541   - },
542 478 };
543 479 },
544 480 },
... ... @@ -547,11 +483,11 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
547 483 component: 'Input',
548 484 label: '命令',
549 485 colProps: { span: 8 },
550   - rules: [{ required: true, message: '请输入开下发命令' }],
  486 + rules: [{ validator: validateTCPCustomCommand }],
551 487 // 是控制组件 && 自定义命令 && 传输协议为TCP
552 488 ifShow: ({ model }) =>
553 489 isControlComponent(category!) &&
554   - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM.toString() &&
  490 + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM &&
555 491 model[DataSourceField.TRANSPORT_TYPE] &&
556 492 isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),
557 493 componentProps: {
... ... @@ -563,11 +499,11 @@ export const commonDataSourceSchemas = (): FormSchema[] => {
563 499 component: 'Input',
564 500 label: '命令',
565 501 colProps: { span: 8 },
566   - rules: [{ required: true, message: '请输入关下发命令' }],
  502 + rules: [{ validator: validateTCPCustomCommand }],
567 503 // 是控制组件 && 自定义命令 && 传输协议为TCP
568 504 ifShow: ({ model }) =>
569 505 isControlComponent(category!) &&
570   - model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM.toString() &&
  506 + model[DataSourceField.COMMAND_TYPE] === CommandTypeEnum.CUSTOM &&
571 507 model[DataSourceField.TRANSPORT_TYPE] &&
572 508 isTcpProfile(model[DataSourceField.TRANSPORT_TYPE]),
573 509 componentProps: {
... ...
... ... @@ -24,6 +24,7 @@ import {
24 24 } from './useSocket.type';
25 25 import { ComponentPropsConfigType } from '../../index.type';
26 26 import { isNullOrUnDef } from '/@/utils/is';
  27 +import { CategoryEnum } from '../../components';
27 28
28 29 interface DeviceGroupMapType {
29 30 subscriptionId: number;
... ... @@ -410,8 +411,20 @@ export const useMultipleDataFetch = (
410 411 Object.keys(unref(getDataSourceGroup)).forEach((key) => {
411 412 const item = unref(getDataSourceGroup)[key];
412 413 const attributes = [...new Set(item.map((item) => item.attribute))];
  414 +
  415 + if (props.config.componentConfig.category === CategoryEnum.MAP) {
  416 + const positionIdentifier = item.reduce((prev, next) => {
  417 + const [lat] = next.latitudeIdentifier || [];
  418 + const [lng] = next.longitudeIdentifier || [];
  419 +
  420 + return [...prev, lat, lng];
  421 + }, []);
  422 +
  423 + attributes.push(...new Set(positionIdentifier));
  424 + }
  425 +
413 426 subscriber.trackUpdateGroup(key, {
414   - attributes,
  427 + attributes: attributes.filter(Boolean),
415 428 fn: updateFn,
416 429 });
417 430 });
... ...
  1 +import { ref } from 'vue';
  2 +import { getDeviceDetail } from '/@/api/device/deviceManager';
  3 +import { useDeviceProfileQueryContext } from '../../palette/hooks/useDeviceProfileQueryContext';
  4 +import {
  5 + CommandTypeEnum,
  6 + RPCCommandMethodEnum,
  7 + ServiceCallTypeEnum,
  8 + TransportTypeEnum,
  9 +} from '/@/enums/deviceEnum';
  10 +import { RpcCommandType } from '/@/api/device/model/deviceConfigModel';
  11 +import { useMessage } from '/@/hooks/web/useMessage';
  12 +import { useGetModbusCommand } from './useGetModbusCommand';
  13 +import { TaskTypeEnum } from '/@/views/task/center/config';
  14 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
  15 +import { sendCommandOneway, sendCommandTwoway } from '/@/api/dataBoard';
  16 +import { isNullOrUnDef } from '/@/utils/is';
  17 +
  18 +export interface DoCommandDeliverDataSourceType {
  19 + deviceId: string;
  20 + deviceProfileId: string;
  21 + attribute: string;
  22 + commandType?: CommandTypeEnum;
  23 + openCommand?: string;
  24 + closeCommand?: string;
  25 + openService?: string;
  26 + closeService?: string;
  27 +}
  28 +
  29 +export function useCommandDelivery() {
  30 + const loading = ref(false);
  31 +
  32 + const { createMessage } = useMessage();
  33 +
  34 + const { getDeviceProfileTslByIdWithIdentifier } = useDeviceProfileQueryContext();
  35 +
  36 + const doGetDeviceId = async (deviceId: string) => {
  37 + return await getDeviceDetail(deviceId);
  38 + };
  39 +
  40 + const { validateCanGetCommand, getModbusCommand } = useGetModbusCommand();
  41 +
  42 + const doCommandDeliver = async (
  43 + dataSource: DoCommandDeliverDataSourceType,
  44 + value: any
  45 + ): Promise<boolean> => {
  46 + try {
  47 + const { deviceId, deviceProfileId, attribute, commandType } = dataSource;
  48 +
  49 + loading.value = true;
  50 + const tsl = getDeviceProfileTslByIdWithIdentifier?.(deviceProfileId, attribute);
  51 + const deviceDetail = await doGetDeviceId(deviceId);
  52 +
  53 + let sendCommandFn = sendCommandOneway;
  54 +
  55 + let params: string | Recordable | undefined = {
  56 + [attribute]: value,
  57 + };
  58 +
  59 + if (deviceDetail.transportType === TransportTypeEnum.TCP) {
  60 + if (deviceDetail.codeType === TaskTypeEnum.MODBUS_RTU) {
  61 + if (!validateCanGetCommand(tsl?.extensionDesc, deviceDetail.code)) return false;
  62 + params = await getModbusCommand(value, tsl!.extensionDesc, deviceDetail.code!);
  63 + } else if (deviceDetail.codeType === TaskTypeEnum.CUSTOM) {
  64 + params = value;
  65 + if (commandType === CommandTypeEnum.CUSTOM) {
  66 + if (tsl?.specs?.dataType.type === DataTypeEnum.BOOL) {
  67 + const { openCommand, closeCommand } = dataSource;
  68 + params = !!Number(value) ? openCommand : closeCommand;
  69 + }
  70 + } else if (commandType === CommandTypeEnum.SERVICE) {
  71 + if (tsl?.specs?.dataType.type === DataTypeEnum.BOOL) {
  72 + const { openService, closeService } = dataSource;
  73 + const serviceIdentifier = !!Number(value) ? openService : closeService;
  74 + const serviceTsl = getDeviceProfileTslByIdWithIdentifier?.(
  75 + deviceProfileId,
  76 + serviceIdentifier!
  77 + );
  78 + params = serviceTsl?.inputData?.[0].serviceCommand;
  79 + sendCommandFn =
  80 + serviceTsl?.callType === ServiceCallTypeEnum.SYNC
  81 + ? sendCommandTwoway
  82 + : sendCommandOneway;
  83 + }
  84 + }
  85 + }
  86 + }
  87 +
  88 + if (isNullOrUnDef(params)) return false;
  89 +
  90 + const rpcCommand: RpcCommandType = {
  91 + additionalInfo: {
  92 + cmdType: CommandTypeEnum.API,
  93 + },
  94 + params,
  95 + method: RPCCommandMethodEnum.THINGSKIT,
  96 + persistent: true,
  97 + };
  98 +
  99 + await sendCommandFn({ deviceId: deviceId, value: rpcCommand });
  100 +
  101 + createMessage.success('命令下发成功');
  102 +
  103 + return true;
  104 + } catch (e) {
  105 + return false;
  106 + } finally {
  107 + loading.value = false;
  108 + }
  109 + };
  110 +
  111 + return {
  112 + loading,
  113 + doCommandDeliver,
  114 + };
  115 +}
... ...
  1 +import { ExtensionDesc } from '/@/api/device/model/modelOfMatterModel';
  2 +import { genModbusCommand } from '/@/api/task';
  3 +import { GenModbusCommandType } from '/@/api/task/model';
  4 +import { ModbusCRCEnum, RegisterActionTypeEnum } from '/@/enums/objectModelEnum';
  5 +import { useMessage } from '/@/hooks/web/useMessage';
  6 +
  7 +export const InsertString = (t: any, c: any, n: any) => {
  8 + const r: string | number[] = [];
  9 +
  10 + for (let i = 0; i * 2 < t.length; i++) r.push(t.substr(i * 2, n));
  11 +
  12 + return r.join(c);
  13 +};
  14 +
  15 +export const FillString = (t: any, c: any, n: any, b: any) => {
  16 + if (t === '' || c.length !== 1 || n <= t.length) return t;
  17 +
  18 + const l = t.length;
  19 +
  20 + for (let i = 0; i < n - l; i++) {
  21 + if (b === true) t = c + t;
  22 + else t += c;
  23 + }
  24 + return t;
  25 +};
  26 +
  27 +export const SingleToHex = (t: any) => {
  28 + if (t === '') return '';
  29 +
  30 + t = parseFloat(t);
  31 +
  32 + if (isNaN(t) === true) return 'Error';
  33 +
  34 + if (t === 0) return '00000000';
  35 +
  36 + let s, e, m;
  37 +
  38 + if (t > 0) {
  39 + s = 0;
  40 + } else {
  41 + s = 1;
  42 +
  43 + t = 0 - t;
  44 + }
  45 + m = t.toString(2);
  46 +
  47 + if (m >= 1) {
  48 + if (m.indexOf('.') === -1) m = `${m}.0`;
  49 +
  50 + e = m.indexOf('.') - 1;
  51 + } else {
  52 + e = 1 - m.indexOf('1');
  53 + }
  54 + if (e >= 0) m = m.replace('.', '');
  55 + else m = m.substring(m.indexOf('1'));
  56 +
  57 + if (m.length > 24) m = m.substr(0, 24);
  58 + else m = FillString(m, '0', 24, false);
  59 +
  60 + m = m.substring(1);
  61 +
  62 + e = (e + 127).toString(2);
  63 +
  64 + e = FillString(e, '0', 8, true);
  65 +
  66 + let r = parseInt(s + e + m, 2).toString(16);
  67 +
  68 + r = FillString(r, '0', 8, true);
  69 +
  70 + return InsertString(r, ' ', 2).toUpperCase();
  71 +};
  72 +
  73 +export const FormatHex = (t: any, n: any, ie: any) => {
  74 + const r: string[] = [];
  75 +
  76 + let s = '';
  77 +
  78 + let c = 0;
  79 +
  80 + for (let i = 0; i < t.length; i++) {
  81 + if (t.charAt(i) !== ' ') {
  82 + s += t.charAt(i);
  83 +
  84 + c += 1;
  85 +
  86 + if (c === n) {
  87 + r.push(s);
  88 +
  89 + s = '';
  90 +
  91 + c = 0;
  92 + }
  93 + }
  94 + if (ie === false) {
  95 + if (i === t.length - 1 && s !== '') r.push(s);
  96 + }
  97 + }
  98 + return r.join('\n');
  99 +};
  100 +
  101 +export const FormatHexBatch = (t: any, n: any, ie: any) => {
  102 + const a = t.split('\n');
  103 +
  104 + const r: string[] = [];
  105 +
  106 + for (let i = 0; i < a.length; i++) r[i] = FormatHex(a[i], n, ie);
  107 +
  108 + return r.join('\n');
  109 +};
  110 +
  111 +export const SingleToHexBatch = (t: any) => {
  112 + const a = t.split('\n');
  113 +
  114 + const r: string[] = [];
  115 +
  116 + for (let i = 0; i < a.length; i++) r[i] = SingleToHex(a[i]);
  117 +
  118 + return r.join('\r\n');
  119 +};
  120 +
  121 +const getArray = (values: any) => {
  122 + const str = values.replace(/\s+/g, '');
  123 + const array: any = [];
  124 +
  125 + for (let i = 0; i < str.length; i += 4) {
  126 + const chunk = parseInt(str.substring(i, i + 4), 16);
  127 + array.push(chunk);
  128 + }
  129 + return array;
  130 +};
  131 +
  132 +const getFloatPart = (number: string | number) => {
  133 + const isLessZero = Number(number) < 0;
  134 + number = number.toString();
  135 + const floatPartStartIndex = number.indexOf('.');
  136 + const value = ~floatPartStartIndex
  137 + ? `${isLessZero ? '-' : ''}0.${number.substring(floatPartStartIndex + 1)}`
  138 + : '0';
  139 + return Number(value);
  140 +};
  141 +
  142 +const REGISTER_MAX_VALUE = Number(0xffff);
  143 +
  144 +export function useGetModbusCommand() {
  145 + const { createMessage } = useMessage();
  146 +
  147 + const getModbusCommand = async (
  148 + value: number,
  149 + extensionDesc: ExtensionDesc,
  150 + deviceAddressCode: string
  151 + ) => {
  152 + const { registerAddress, actionType, zoomFactor } = extensionDesc as Required<ExtensionDesc>;
  153 +
  154 + const params: GenModbusCommandType = {
  155 + crc: ModbusCRCEnum.CRC_16_LOWER,
  156 + registerNumber: 1,
  157 + deviceCode: deviceAddressCode,
  158 + registerAddress,
  159 + method: actionType,
  160 + registerValues: [value],
  161 + };
  162 +
  163 + if (actionType === RegisterActionTypeEnum.INT) {
  164 + const newValue = Math.trunc(value) * zoomFactor + getFloatPart(value) * zoomFactor;
  165 +
  166 + if (newValue % 1 !== 0) {
  167 + createMessage.warning(`属性下发类型必须是整数,缩放因子为${zoomFactor}`);
  168 + return;
  169 + }
  170 +
  171 + if (value * zoomFactor > REGISTER_MAX_VALUE) {
  172 + createMessage.warning(`属性下发值不能超过${REGISTER_MAX_VALUE},缩放因子为${zoomFactor}`);
  173 + return;
  174 + }
  175 +
  176 + params.registerValues = [newValue];
  177 + } else if (actionType === RegisterActionTypeEnum.DOUBLE) {
  178 + const regex = /^-?\d+(\.\d{0,2})?$/;
  179 + const values = Math.trunc(value) * zoomFactor + getFloatPart(value) * zoomFactor;
  180 +
  181 + if (!regex.test(values.toString())) {
  182 + createMessage.warning(`属性下发值精确到两位小数,缩放因子为${zoomFactor}`);
  183 + return;
  184 + }
  185 +
  186 + const newValue = values === 0 ? [0, 0] : getArray(SingleToHex(values));
  187 + params.registerValues = newValue;
  188 + params.registerNumber = 2;
  189 + params.method = '10';
  190 + }
  191 +
  192 + return await genModbusCommand(params);
  193 + };
  194 +
  195 + /**
  196 + *
  197 + * @param extensionDesc 物模型拓展描述符
  198 + * @param deviceAddressCode 设备地址码
  199 + */
  200 + const validateCanGetCommand = (
  201 + extensionDesc?: ExtensionDesc,
  202 + deviceAddressCode?: string,
  203 + createValidateMessage = true
  204 + ) => {
  205 + const result = { flag: true, message: '' };
  206 + if (!extensionDesc) {
  207 + result.flag = false;
  208 + result.message = '当前物模型扩展描述没有填写';
  209 + }
  210 +
  211 + if (!deviceAddressCode) {
  212 + result.flag = false;
  213 + result.message = '当前设备未绑定设备地址码';
  214 + }
  215 +
  216 + if (result.message && createValidateMessage) createMessage.warning(result.message);
  217 +
  218 + return result;
  219 + };
  220 +
  221 + return {
  222 + getModbusCommand,
  223 + validateCanGetCommand,
  224 + };
  225 +}
... ...
1   -import { ref } from 'vue';
2   -import { DataSource } from '../../palette/types';
3   -import { sendCommandOneway, sendCommandTwoway } from '/@/api/dataBoard';
4   -import { useMessage } from '/@/hooks/web/useMessage';
5   -import { TransportTypeEnum, ServiceCallTypeEnum } from '/@/enums/deviceEnum';
6   -
7   -const { createMessage } = useMessage();
8   -export function useSendCommand() {
9   - const loading = ref(false);
10   -
11   - const error = () => {
12   - // createMessage.error('下发指令失败');
13   - return false;
14   - };
15   -
16   - const sendCommand = async (record: DataSource, value: any, isModbusCommand = false) => {
17   - if (!record) return false;
18   - const { customCommand, attribute } = record || {};
19   - const { deviceId } = record;
20   - if (!deviceId) return false;
21   -
22   - try {
23   - loading.value = true;
24   - let params: string | Recordable = {
25   - [attribute!]: Number(value),
26   - };
27   - if (isModbusCommand) {
28   - params = value;
29   - }
30   -
31   - let sendCommandFn = sendCommandOneway;
32   - // 如果是TCP设备从物模型中获取下发命令(TCP网关子设备无物模型服务与事件)
33   - if (customCommand?.transportType === TransportTypeEnum.TCP && !isModbusCommand) {
34   - params = customCommand.command!;
35   - if (customCommand.callType === ServiceCallTypeEnum.SYNC) {
36   - sendCommandFn = sendCommandTwoway;
37   - }
38   - }
39   - // 控制按钮下发命令为0 或 1
40   - await sendCommandFn({
41   - deviceId,
42   - value: {
43   - params: params || null,
44   - persistent: true,
45   - additionalInfo: {
46   - cmdType: customCommand.commandType || 'API',
47   - },
48   - method: 'methodThingskit',
49   - },
50   - });
51   - createMessage.success('命令下发成功');
52   - return true;
53   - } catch (msg) {
54   - console.error(msg);
55   - return error();
56   - } finally {
57   - loading.value = false;
58   - }
59   - };
60   - return {
61   - loading,
62   - sendCommand,
63   - };
64   -}
... ... @@ -9,7 +9,7 @@ export const useApp = () => {
9 9
10 10 const isPhone = () => {
11 11 const values = location?.pathname.split('/') || [];
12   - return values[values?.length - 2] === 'phone' ? true : false;
  12 + return values[values?.length - 2] === 'phone';
13 13 };
14 14
15 15 return { getIsAppPage, isPhone };
... ...
... ... @@ -11,6 +11,8 @@ import {
11 11 } from '../types';
12 12 import { buildUUID } from '/@/utils/uuid';
13 13 import { PublicComponentOptions } from '../../packages/index.type';
  14 +import { batchGetObjectModel } from '/@/api/device/modelOfMatter';
  15 +import { BatchGetObjectModelItemType } from '/@/api/device/model/modelOfMatterModel';
14 16
15 17 export interface WidgetDataType extends Layout, ComponentDataType, PublicComponentOptions {}
16 18
... ... @@ -23,6 +25,17 @@ export const useDataSource = (propsRef: ComputedRef<Recordable>) => {
23 25
24 26 const dataSource = ref<WidgetDataType[]>([]);
25 27
  28 + const deviceProfilesMapRef = ref<Map<string, BatchGetObjectModelItemType>>();
  29 +
  30 + const getAllDeviceProfileIdsByDataSource = computed(() => {
  31 + const result = unref(rawDataSource)?.componentData?.reduce((prev, next) => {
  32 + const ids = next.dataSource.map((temp) => temp.deviceProfileId);
  33 + return [...prev, ...ids];
  34 + }, []);
  35 +
  36 + return [...new Set(result)];
  37 + });
  38 +
26 39 const getSharePageData = computed(() => {
27 40 return unref(propsRef).value!;
28 41 });
... ... @@ -64,19 +77,8 @@ export const useDataSource = (propsRef: ComputedRef<Recordable>) => {
64 77 loading.value = true;
65 78 const { data } = await getDataBoradDetail();
66 79 rawDataSource.value = data;
67   - // const result: WidgetDataType[] = (data.componentData || []).map((item) => {
68   - // const { id } = item;
69   -
70   - // const layout =
71   - // (data.componentLayout || []).find((item) => item.id === id) ||
72   - // ({} as ComponentLayoutType);
73   -
74   - // return {
75   - // i: buildUUID(),
76   - // ...layout,
77   - // ...item,
78   - // } as WidgetDataType;
79   - // });
  80 + await getAllDeviceProfiles();
  81 +
80 82 const result: WidgetDataType[] = data.componentLayout.map((item) => {
81 83 const { id } = item;
82 84 const dataSource =
... ... @@ -129,8 +131,35 @@ export const useDataSource = (propsRef: ComputedRef<Recordable>) => {
129 131 } catch (error) {}
130 132 };
131 133
132   - onMounted(() => {
133   - getDataSource();
  134 + const getAllDeviceProfiles = async () => {
  135 + if (!unref(getAllDeviceProfileIdsByDataSource).length) return;
  136 + const result = await batchGetObjectModel({
  137 + deviceProfileIds: unref(getAllDeviceProfileIdsByDataSource),
  138 + });
  139 +
  140 + const map = new Map();
  141 +
  142 + result.forEach((item) => {
  143 + map.set(item.id, item);
  144 + });
  145 +
  146 + deviceProfilesMapRef.value = map;
  147 + };
  148 +
  149 + function getDeviceProfileDetailById(id: string) {
  150 + return unref(deviceProfilesMapRef)?.get(id);
  151 + }
  152 +
  153 + function getDeviceProfileTslByIdWithIdentifier(id: string, identifier: string) {
  154 + const detail = getDeviceProfileDetailById(id);
  155 + if (!detail) return;
  156 + for (const item of detail.tsl) {
  157 + if (item.identifier === identifier) return item;
  158 + }
  159 + }
  160 +
  161 + onMounted(async () => {
  162 + await getDataSource();
134 163 });
135 164
136 165 return {
... ... @@ -138,8 +167,11 @@ export const useDataSource = (propsRef: ComputedRef<Recordable>) => {
138 167 draggable: !unref(getIsSharePage) && !unref(getIsAppPage),
139 168 resizable: !unref(getIsSharePage) && !unref(getIsAppPage),
140 169 dataSource,
  170 + deviceProfilesMapRef,
141 171 rawDataSource,
142 172 getDataSource,
143 173 setLayoutInfo,
  174 + getDeviceProfileDetailById,
  175 + getDeviceProfileTslByIdWithIdentifier,
144 176 };
145 177 };
... ...
  1 +import { inject, provide } from 'vue';
  2 +import { BatchGetObjectModelItemType, Tsl } from '/@/api/device/model/modelOfMatterModel';
  3 +
  4 +const SymbolKey = Symbol('data-board');
  5 +
  6 +export interface ContextOptionsType {
  7 + getDeviceProfileDetailById: (id: string) => BatchGetObjectModelItemType | undefined;
  8 + getDeviceProfileTslByIdWithIdentifier: (id: string, identifier?: string) => Tsl | undefined;
  9 +}
  10 +
  11 +export const createDeviceProfileQueryContext = (options: ContextOptionsType) => {
  12 + provide(SymbolKey, options);
  13 +};
  14 +
  15 +export const useDeviceProfileQueryContext = () => {
  16 + return inject<ContextOptionsType>(SymbolKey) || ({} as Partial<ContextOptionsType>);
  17 +};
... ...
  1 +import { DataType, Specs, StructJSON } from '/@/api/device/model/modelOfMatterModel';
  2 +import { DataTypeEnum } from '/@/enums/objectModelEnum';
  3 +import { useJsonParse } from '/@/hooks/business/useJsonParse';
  4 +function getBoolTypeValue(value: number, Specs: Specs) {
  5 + const { boolOpen, boolClose } = Specs;
  6 +
  7 + return Number(value) ? boolOpen : boolClose;
  8 +}
  9 +
  10 +function getEnumTypeValue(value: number, specsList: Specs[]) {
  11 + const res = specsList.find((item) => item.value === Number(value));
  12 +
  13 + return res?.name;
  14 +}
  15 +
  16 +function getStructTypeValue(value: string, specs: StructJSON[]): string {
  17 + const res = useJsonParse(value).value;
  18 +
  19 + function generateStruct(specs: StructJSON[], value: Recordable) {
  20 + if (!value) return {};
  21 +
  22 + return specs.reduce((prev, next) => {
  23 + return {
  24 + ...prev,
  25 + [next.functionName!]: getValueByType(
  26 + next.dataType!.type,
  27 + value[next.identifier],
  28 + next.dataType!
  29 + ),
  30 + };
  31 + }, {});
  32 + }
  33 +
  34 + return JSON.stringify(generateStruct(specs, res));
  35 +}
  36 +
  37 +function getValueByType(type: string, value: any, dataType: DataType) {
  38 + switch (type) {
  39 + case DataTypeEnum.BOOL:
  40 + return getBoolTypeValue(value, dataType.specs as Specs);
  41 + case DataTypeEnum.STRUCT:
  42 + return getStructTypeValue(value, dataType.specs as StructJSON[]);
  43 + case DataTypeEnum.ENUM:
  44 + return getEnumTypeValue(value, dataType.specsList as Specs[]);
  45 + default:
  46 + return value;
  47 + }
  48 +}
  49 +export function useThingsModelValueTransform(value: any, thingsModelDataType?: DataType) {
  50 + if (!thingsModelDataType) return value;
  51 +
  52 + const { type } = thingsModelDataType;
  53 + return getValueByType(type, value, thingsModelDataType);
  54 +}
... ...
... ... @@ -40,6 +40,7 @@
40 40 import SIGNALSVG from '/@/assets/svg/signal.svg';
41 41 import BATTERYSVG from '/@/assets/svg/battery.svg';
42 42 import { useRoute } from 'vue-router';
  43 + import { createDeviceProfileQueryContext } from './hooks/useDeviceProfileQueryContext';
43 44
44 45 const props = defineProps<{
45 46 value?: Recordable;
... ... @@ -53,8 +54,17 @@
53 54
54 55 const ROUTE = useRoute();
55 56
56   - const { loading, draggable, resizable, dataSource, rawDataSource, setLayoutInfo, getDataSource } =
57   - useDataSource(getProps);
  57 + const {
  58 + loading,
  59 + draggable,
  60 + resizable,
  61 + dataSource,
  62 + rawDataSource,
  63 + setLayoutInfo,
  64 + getDataSource,
  65 + getDeviceProfileDetailById,
  66 + getDeviceProfileTslByIdWithIdentifier,
  67 + } = useDataSource(getProps);
58 68
59 69 const { resize, resized, moved, containerResized } = useDragGridLayout(dataSource, setLayoutInfo);
60 70
... ... @@ -157,6 +167,11 @@
157 167
158 168 createDataBoardContext({ send, close });
159 169
  170 + createDeviceProfileQueryContext({
  171 + getDeviceProfileDetailById,
  172 + getDeviceProfileTslByIdWithIdentifier,
  173 + });
  174 +
160 175 const { getDarkMode } = useRootSetting();
161 176 watch(
162 177 getIsSharePage,
... ...
1 1 import { PublicComponentOptions } from '../../packages/index.type';
  2 +import { CommandTypeEnum } from '/@/enums/deviceEnum';
2 3
3 4 export interface ComponentDataType<T = ExtraDataSource> {
4 5 id: string;
... ... @@ -19,19 +20,26 @@ export interface DataSource {
19 20 deviceId: string;
20 21 deviceType: string;
21 22 attribute: string;
22   - attributeName: string;
23   - deviceName: string;
24   - gatewayDevice: boolean;
25   - slaveDeviceId: string;
  23 + commandType?: CommandTypeEnum;
  24 + openCommand?: string;
  25 + closeCommand?: string;
  26 + openService?: string;
  27 + closeService?: string;
  28 + // attributeName: string;
  29 + // deviceName: string;
  30 + // gatewayDevice: boolean;
  31 + // slaveDeviceId: string;
26 32 deviceRename: string;
27 33 attributeRename: string;
28 34 componentInfo: ComponentInfo;
29   - customCommand: CustomCommand;
30 35 videoConfig?: VideoConfigType;
31 36 [key: string]: any;
32   - lal?: string;
33   - latitude?: string | number;
34   - longitude?: string | number;
  37 +
  38 + latitudeIdentifier?: string[];
  39 + longitudeIdentifier?: string[];
  40 + // lal?: string;
  41 + // latitude?: string | number;
  42 + // longitude?: string | number;
35 43 }
36 44
37 45 export interface ExtraDataSource extends DataSource, PublicComponentOptions {
... ...