Commit eca2a6378984c21144ac34954df08eb64e67f373

Authored by ww
1 parent ef367005

fix: DEFECT-1113 修复看板管理控制组件数据回显问题

1 <script lang="ts"> 1 <script lang="ts">
2 export default { 2 export default {
  3 + components: { Spin },
3 inheritAttrs: false, 4 inheritAttrs: false,
4 }; 5 };
5 </script> 6 </script>
6 <script lang="ts" setup> 7 <script lang="ts" setup>
  8 + import { Spin } from 'ant-design-vue';
7 import { RadioRecord } from '../../detail/config/util'; 9 import { RadioRecord } from '../../detail/config/util';
8 import { ControlComponentDefaultConfig, ControlComponentValue } from './control.config'; 10 import { ControlComponentDefaultConfig, ControlComponentValue } from './control.config';
9 import { useSendCommand } from './useSendCommand'; 11 import { useSendCommand } from './useSendCommand';
  12 + import { ref } from 'vue';
10 13
11 interface VisualComponentProps<Layout = Recordable, Value = ControlComponentValue> { 14 interface VisualComponentProps<Layout = Recordable, Value = ControlComponentValue> {
12 value?: Value; 15 value?: Value;
@@ -23,33 +26,45 @@ @@ -23,33 +26,45 @@
23 const emit = defineEmits(['update:value', 'change']); 26 const emit = defineEmits(['update:value', 'change']);
24 27
25 const { sendCommand } = useSendCommand(); 28 const { sendCommand } = useSendCommand();
26 - const handleChange = (event: Event) => { 29 +
  30 + const loading = ref(false);
  31 + const handleChange = async (event: Event) => {
27 const _value = (event.target as HTMLInputElement).checked; 32 const _value = (event.target as HTMLInputElement).checked;
  33 + if (props.value) {
  34 + loading.value = true;
  35 + const flag = await sendCommand(props.value, _value);
  36 + loading.value = false;
  37 + if (!flag) {
  38 + (event.target as HTMLInputElement).checked = !_value;
  39 + return;
  40 + }
  41 + }
28 emit('update:value', _value); 42 emit('update:value', _value);
29 emit('change', _value); 43 emit('change', _value);
30 - sendCommand(props.value!, _value);  
31 }; 44 };
32 </script> 45 </script>
33 46
34 <template> 47 <template>
35 <div class="flex flex-col justify-center"> 48 <div class="flex flex-col justify-center">
36 - <label class="sliding-switch">  
37 - <input  
38 - :value="!!Number(props.value?.value)"  
39 - type="checkbox"  
40 - :checked="!!Number(props.value?.value)"  
41 - @change="handleChange"  
42 - />  
43 - <span class="slider"></span>  
44 - <span class="on">ON</span>  
45 - <span class="off">OFF</span>  
46 - </label>  
47 - <div  
48 - class="text-center mt-2 text-gray-700"  
49 - :style="{ color: props?.value?.fontColor || ControlComponentDefaultConfig.fontColor }"  
50 - >  
51 - {{ props.value?.attributeRename || props.value?.attribute }}</div  
52 - > 49 + <Spin :spinning="loading">
  50 + <label class="sliding-switch">
  51 + <input
  52 + :value="!!Number(props.value?.value)"
  53 + type="checkbox"
  54 + :checked="!!Number(props.value?.value)"
  55 + @change="handleChange"
  56 + />
  57 + <span class="slider"></span>
  58 + <span class="on">ON</span>
  59 + <span class="off">OFF</span>
  60 + </label>
  61 + <div
  62 + class="text-center mt-2 text-gray-700"
  63 + :style="{ color: props?.value?.fontColor || ControlComponentDefaultConfig.fontColor }"
  64 + >
  65 + {{ props.value?.attributeRename || props.value?.attribute }}</div
  66 + >
  67 + </Spin>
53 </div> 68 </div>
54 </template> 69 </template>
55 70
@@ -31,8 +31,14 @@ @@ -31,8 +31,14 @@
31 const checked = ref(!!Number(props.value.value)); 31 const checked = ref(!!Number(props.value.value));
32 32
33 const { sendCommand } = useSendCommand(); 33 const { sendCommand } = useSendCommand();
34 - const handleChange = (value: boolean) => {  
35 - sendCommand(props.value, value); 34 + const loading = ref(false);
  35 + const handleChange = async (value: boolean) => {
  36 + loading.value = true;
  37 + const flag = await sendCommand(props.value, value);
  38 + loading.value = false;
  39 + if (!flag) {
  40 + checked.value = !value;
  41 + }
36 }; 42 };
37 43
38 watchEffect(() => { 44 watchEffect(() => {
@@ -59,6 +65,6 @@ @@ -59,6 +65,6 @@
59 {{ props.value.attributeRename || props.value.attribute }} 65 {{ props.value.attributeRename || props.value.attribute }}
60 </span> 66 </span>
61 </div> 67 </div>
62 - <Switch v-model:checked="checked" @change="handleChange" /> 68 + <Switch v-model:checked="checked" :loading="loading" @change="handleChange" />
63 </div> 69 </div>
64 </template> 70 </template>
1 <script lang="ts"> 1 <script lang="ts">
2 export default { 2 export default {
  3 + components: { Spin },
3 inheritAttrs: false, 4 inheritAttrs: false,
4 }; 5 };
5 </script> 6 </script>
@@ -8,6 +9,8 @@ @@ -8,6 +9,8 @@
8 import { DEFAULT_RADIO_RECORD, fontSize, RadioRecord } from '../../detail/config/util'; 9 import { DEFAULT_RADIO_RECORD, fontSize, RadioRecord } from '../../detail/config/util';
9 import { ControlComponentDefaultConfig, ControlComponentValue } from './control.config'; 10 import { ControlComponentDefaultConfig, ControlComponentValue } from './control.config';
10 import { useSendCommand } from './useSendCommand'; 11 import { useSendCommand } from './useSendCommand';
  12 + import { ref } from 'vue';
  13 + import { Spin } from 'ant-design-vue';
11 14
12 const props = defineProps<{ 15 const props = defineProps<{
13 value?: ControlComponentValue; 16 value?: ControlComponentValue;
@@ -18,11 +21,20 @@ @@ -18,11 +21,20 @@
18 const emit = defineEmits(['update:value', 'change']); 21 const emit = defineEmits(['update:value', 'change']);
19 22
20 const { sendCommand } = useSendCommand(); 23 const { sendCommand } = useSendCommand();
21 - const handleChange = (event: Event) => { 24 + const loading = ref(false);
  25 + const handleChange = async (event: Event) => {
22 const _value = (event.target as HTMLInputElement).checked; 26 const _value = (event.target as HTMLInputElement).checked;
  27 + if (props.value) {
  28 + loading.value = true;
  29 + const flag = await sendCommand(props.value, _value);
  30 + loading.value = false;
  31 + if (!flag) {
  32 + (event.target as HTMLInputElement).checked = !_value;
  33 + return;
  34 + }
  35 + }
23 emit('update:value', _value); 36 emit('update:value', _value);
24 emit('change', _value); 37 emit('change', _value);
25 - sendCommand(props.value!, _value);  
26 }; 38 };
27 39
28 const getRadio = computed(() => { 40 const getRadio = computed(() => {
@@ -32,35 +44,37 @@ @@ -32,35 +44,37 @@
32 44
33 <template> 45 <template>
34 <div class="flex flex-col"> 46 <div class="flex flex-col">
35 - <div  
36 - class="toggle-switch"  
37 - :style="{  
38 - width: fontSize({ radioRecord: getRadio, basic: 75, max: 75, min: 60 }),  
39 - height: fontSize({ radioRecord: getRadio, basic: 97.5, max: 97.5, min: 80 }),  
40 - }"  
41 - >  
42 - <label class="switch">  
43 - <input  
44 - :value="!!Number(props.value?.value)"  
45 - type="checkbox"  
46 - :checked="!!Number(props.value?.value)"  
47 - @change="handleChange"  
48 - />  
49 - <div class="button">  
50 - <div class="light"></div>  
51 - <div class="dots"></div>  
52 - <div class="characters"></div>  
53 - <div class="shine"></div>  
54 - <div class="shadow"></div>  
55 - </div>  
56 - </label>  
57 - </div>  
58 - <div  
59 - class="text-center mt-2 text-gray-700"  
60 - :style="{ color: props?.value?.fontColor || ControlComponentDefaultConfig.fontColor }"  
61 - >  
62 - {{ props.value?.attributeRename || props.value?.attribute }}</div  
63 - > 47 + <Spin :spinning="loading">
  48 + <div
  49 + class="toggle-switch"
  50 + :style="{
  51 + width: fontSize({ radioRecord: getRadio, basic: 75, max: 75, min: 60 }),
  52 + height: fontSize({ radioRecord: getRadio, basic: 97.5, max: 97.5, min: 80 }),
  53 + }"
  54 + >
  55 + <label class="switch">
  56 + <input
  57 + :value="!!Number(props.value?.value)"
  58 + type="checkbox"
  59 + :checked="!!Number(props.value?.value)"
  60 + @change="handleChange"
  61 + />
  62 + <div class="button">
  63 + <div class="light"></div>
  64 + <div class="dots"></div>
  65 + <div class="characters"></div>
  66 + <div class="shine"></div>
  67 + <div class="shadow"></div>
  68 + </div>
  69 + </label>
  70 + </div>
  71 + <div
  72 + class="text-center mt-2 text-gray-700"
  73 + :style="{ color: props?.value?.fontColor || ControlComponentDefaultConfig.fontColor }"
  74 + >
  75 + {{ props.value?.attributeRename || props.value?.attribute }}</div
  76 + >
  77 + </Spin>
64 </div> 78 </div>
65 </template> 79 </template>
66 80
@@ -5,33 +5,44 @@ import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; @@ -5,33 +5,44 @@ import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
5 import { getModelServices } from '/@/api/device/modelOfMatter'; 5 import { getModelServices } from '/@/api/device/modelOfMatter';
6 import { useMessage } from '/@/hooks/web/useMessage'; 6 import { useMessage } from '/@/hooks/web/useMessage';
7 import { isString } from '/@/utils/is'; 7 import { isString } from '/@/utils/is';
  8 +import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
8 9
9 const { createMessage } = useMessage(); 10 const { createMessage } = useMessage();
10 export function useSendCommand() { 11 export function useSendCommand() {
  12 + const error = () => {
  13 + createMessage.error('下发指令失败');
  14 + return false;
  15 + };
11 const sendCommand = async (record: ControlComponentValue, value: any) => { 16 const sendCommand = async (record: ControlComponentValue, value: any) => {
12 - if (!record) return; 17 + if (!record) return error();
13 const { deviceProfileId, attribute, deviceType } = record; 18 const { deviceProfileId, attribute, deviceType } = record;
14 let { deviceId } = record; 19 let { deviceId } = record;
15 - if (!deviceId) return; 20 + if (!deviceId) return error();
16 try { 21 try {
17 const list = await getDeviceProfile(); 22 const list = await getDeviceProfile();
18 const deviceProfile = list.find((item) => item.id === deviceProfileId); 23 const deviceProfile = list.find((item) => item.id === deviceProfileId);
19 - if (!deviceProfile) return; 24 + if (!deviceProfile) return error();
  25 +
20 let params: string | Recordable = { 26 let params: string | Recordable = {
21 [attribute!]: Number(value), 27 [attribute!]: Number(value),
22 }; 28 };
23 - if (deviceProfile.transportType === 'TCP') {  
24 - const serviceList = await getModelServices({ deviceProfileId: deviceProfileId! }); 29 +
  30 + // 如果是TCP设备从物模型中获取下发命令(TCP网关子设备无物模型服务与事件)
  31 + if (deviceProfile!.transportType === TransportTypeEnum.TCP) {
  32 + const serviceList = (await getModelServices({ deviceProfileId: deviceProfileId! })) || [];
25 const record = serviceList.find((item) => item.identifier === attribute); 33 const record = serviceList.find((item) => item.identifier === attribute);
26 const sendCommand = record?.functionJson.inputData?.at(0)?.serviceCommand || ''; 34 const sendCommand = record?.functionJson.inputData?.at(0)?.serviceCommand || '';
27 params = isString(sendCommand) ? sendCommand : JSON.stringify(sendCommand); 35 params = isString(sendCommand) ? sendCommand : JSON.stringify(sendCommand);
28 } 36 }
  37 +
29 if (deviceType === DeviceTypeEnum.SENSOR) { 38 if (deviceType === DeviceTypeEnum.SENSOR) {
30 deviceId = await getDeviceRelation({ 39 deviceId = await getDeviceRelation({
31 deviceId, 40 deviceId,
32 isSlave: deviceType === DeviceTypeEnum.SENSOR, 41 isSlave: deviceType === DeviceTypeEnum.SENSOR,
33 }); 42 });
34 } 43 }
  44 +
  45 + // 控制按钮下发命令为0 或 1
35 await sendCommandOneway({ 46 await sendCommandOneway({
36 deviceId, 47 deviceId,
37 value: { 48 value: {
@@ -44,7 +55,11 @@ export function useSendCommand() { @@ -44,7 +55,11 @@ export function useSendCommand() {
44 }, 55 },
45 }); 56 });
46 createMessage.success('命令下发成功'); 57 createMessage.success('命令下发成功');
47 - } catch (error) {} 58 + } catch (msg) {
  59 + return error();
  60 + } finally {
  61 + return true;
  62 + }
48 }; 63 };
49 return { 64 return {
50 sendCommand, 65 sendCommand,
@@ -57,6 +57,13 @@ @@ -57,6 +57,13 @@
57 } 57 }
58 }; 58 };
59 59
  60 + const resetFormFields = async () => {
  61 + const hasExistEl = Object.keys(dataSourceEl).filter((key) => dataSourceEl[key]);
  62 + for (const id of hasExistEl) {
  63 + await dataSourceEl[id]?.resetFields();
  64 + }
  65 + };
  66 +
60 const validate = async () => { 67 const validate = async () => {
61 await basicMethod.validate(); 68 await basicMethod.validate();
62 await validateDataSourceField(); 69 await validateDataSourceField();
@@ -258,6 +265,16 @@ @@ -258,6 +265,16 @@
258 return isControlComponent(props.frontId as FrontComponent); 265 return isControlComponent(props.frontId as FrontComponent);
259 }); 266 });
260 267
  268 + watch(
  269 + () => props.frontId,
  270 + async (target, oldTarget) => {
  271 + if (isControlComponent(oldTarget!)) return;
  272 + if (isControlComponent(target!)) {
  273 + await resetFormFields();
  274 + }
  275 + }
  276 + );
  277 +
261 onMounted(() => handleSort()); 278 onMounted(() => handleSort());
262 279
263 defineExpose({ 280 defineExpose({
@@ -293,7 +310,7 @@ @@ -293,7 +310,7 @@
293 310
294 <section ref="formListEl"> 311 <section ref="formListEl">
295 <div v-for="item in dataSource" :data-id="item.id" :key="item.id" class="flex bg-light-50"> 312 <div v-for="item in dataSource" :data-id="item.id" :key="item.id" class="flex bg-light-50">
296 - <div class="w-24 text-right flex justify-end"> 选择设备 </div> 313 + <div class="w-24 text-right flex justify-end" style="flex: 0 0 96px"> 选择设备 </div>
297 <div class="pl-2 flex-auto"> 314 <div class="pl-2 flex-auto">
298 <component 315 <component
299 :frontId="$props.frontId" 316 :frontId="$props.frontId"
@@ -9,6 +9,8 @@ import { getModelServices } from '/@/api/device/modelOfMatter'; @@ -9,6 +9,8 @@ import { getModelServices } from '/@/api/device/modelOfMatter';
9 import { findDictItemByCode } from '/@/api/system/dict'; 9 import { findDictItemByCode } from '/@/api/system/dict';
10 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; 10 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
11 import { DataTypeEnum } from '/@/components/Form/src/externalCompns/components/StructForm/config'; 11 import { DataTypeEnum } from '/@/components/Form/src/externalCompns/components/StructForm/config';
  12 +import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
  13 +import { nextTick } from 'vue';
12 14
13 export enum BasicConfigField { 15 export enum BasicConfigField {
14 NAME = 'name', 16 NAME = 'name',
@@ -66,7 +68,7 @@ export const isMapComponent = (frontId: FrontComponent) => { @@ -66,7 +68,7 @@ export const isMapComponent = (frontId: FrontComponent) => {
66 }; 68 };
67 69
68 const isTcpProfile = (transportType: string) => { 70 const isTcpProfile = (transportType: string) => {
69 - return transportType === 'TCP'; 71 + return transportType === TransportTypeEnum.TCP;
70 }; 72 };
71 73
72 export const basicSchema: FormSchema[] = [ 74 export const basicSchema: FormSchema[] = [
@@ -117,6 +119,7 @@ export const dataSourceSchema = (isEdit: boolean, frontId?: FrontComponent): For @@ -117,6 +119,7 @@ export const dataSourceSchema = (isEdit: boolean, frontId?: FrontComponent): For
117 component: 'ApiSelect', 119 component: 'ApiSelect',
118 label: '设备类型', 120 label: '设备类型',
119 colProps: { span: 8 }, 121 colProps: { span: 8 },
  122 + rules: [{ message: '请选择设备类型', required: true }],
120 // defaultValue: DeviceTypeEnum.SENSOR, 123 // defaultValue: DeviceTypeEnum.SENSOR,
121 componentProps: ({ formActionType }) => { 124 componentProps: ({ formActionType }) => {
122 const { setFieldsValue } = formActionType; 125 const { setFieldsValue } = formActionType;
@@ -180,7 +183,7 @@ export const dataSourceSchema = (isEdit: boolean, frontId?: FrontComponent): For @@ -180,7 +183,7 @@ export const dataSourceSchema = (isEdit: boolean, frontId?: FrontComponent): For
180 colProps: { span: 8 }, 183 colProps: { span: 8 },
181 rules: [{ required: true, message: '组织为必填项' }], 184 rules: [{ required: true, message: '组织为必填项' }],
182 componentProps({ formActionType }) { 185 componentProps({ formActionType }) {
183 - const { setFieldsValue } = formActionType; 186 + const { setFieldsValue, getFieldsValue } = formActionType;
184 return { 187 return {
185 placeholder: '请选择组织', 188 placeholder: '请选择组织',
186 api: async () => { 189 api: async () => {
@@ -192,6 +195,9 @@ export const dataSourceSchema = (isEdit: boolean, frontId?: FrontComponent): For @@ -192,6 +195,9 @@ export const dataSourceSchema = (isEdit: boolean, frontId?: FrontComponent): For
192 setFieldsValue({ 195 setFieldsValue({
193 [DataSourceField.DEVICE_ID]: null, 196 [DataSourceField.DEVICE_ID]: null,
194 }); 197 });
  198 + nextTick(() => {
  199 + console.log('org change', getFieldsValue());
  200 + });
195 }, 201 },
196 getPopupContainer: () => document.body, 202 getPopupContainer: () => document.body,
197 }; 203 };
@@ -250,11 +256,24 @@ export const dataSourceSchema = (isEdit: boolean, frontId?: FrontComponent): For @@ -250,11 +256,24 @@ export const dataSourceSchema = (isEdit: boolean, frontId?: FrontComponent): For
250 component: 'ApiSelect', 256 component: 'ApiSelect',
251 label: '属性', 257 label: '属性',
252 colProps: { span: 8 }, 258 colProps: { span: 8 },
253 - rules: [{ required: true, message: '属性为必填项' }], 259 + dynamicRules: ({ model }) => {
  260 + const transportType = model[DataSourceField.TRANSPORT_TYPE];
  261 + return [
  262 + {
  263 + required: true,
  264 + message: `${
  265 + isControlComponent(frontId as FrontComponent) && isTcpProfile(transportType)
  266 + ? '服务'
  267 + : '属性'
  268 + }为必填项`,
  269 + },
  270 + ];
  271 + },
254 componentProps({ formModel }) { 272 componentProps({ formModel }) {
255 const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID]; 273 const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
256 const transportType = formModel[DataSourceField.TRANSPORT_TYPE]; 274 const transportType = formModel[DataSourceField.TRANSPORT_TYPE];
257 - if (isEdit && ![deviceProfileId, transportType].every(Boolean)) return {}; 275 + if (isEdit && ![deviceProfileId, transportType].every(Boolean))
  276 + return { placeholder: '请选择属性', getPopupContainer: () => document.body };
258 return { 277 return {
259 api: async () => { 278 api: async () => {
260 try { 279 try {
@@ -47,7 +47,7 @@ @@ -47,7 +47,7 @@
47 import { BAI_DU_MAP_GL_LIB, BAI_DU_MAP_TRACK_ANIMATION } from '/@/utils/fnUtils'; 47 import { BAI_DU_MAP_GL_LIB, BAI_DU_MAP_TRACK_ANIMATION } from '/@/utils/fnUtils';
48 48
49 const props = defineProps<{ 49 const props = defineProps<{
50 - value: Recordable; 50 + value?: Recordable;
51 }>(); 51 }>();
52 52
53 const ROUTE = useRoute(); 53 const ROUTE = useRoute();
@@ -126,7 +126,7 @@ export function useSocketConnect(dataSourceRef: Ref<DataBoardLayoutInfo[]>) { @@ -126,7 +126,7 @@ export function useSocketConnect(dataSourceRef: Ref<DataBoardLayoutInfo[]>) {
126 const { subscriptionId, data = {} } = res; 126 const { subscriptionId, data = {} } = res;
127 if (isNullAndUnDef(subscriptionId)) return; 127 if (isNullAndUnDef(subscriptionId)) return;
128 const mappingRecord = cmdIdMapping.get(subscriptionId); 128 const mappingRecord = cmdIdMapping.get(subscriptionId);
129 - if (!mappingRecord) return; 129 + if (!mappingRecord || !data) return;
130 mappingRecord.forEach((item) => { 130 mappingRecord.forEach((item) => {
131 const { attribute, recordIndex, dataSourceIndex } = item; 131 const { attribute, recordIndex, dataSourceIndex } = item;
132 const [[timespan, value]] = data[attribute]; 132 const [[timespan, value]] = data[attribute];
@@ -249,17 +249,19 @@ @@ -249,17 +249,19 @@
249 </div> 249 </div>
250 </div> 250 </div>
251 <div class="flex justify-between mt-4 text-sm" style="color: #999"> 251 <div class="flex justify-between mt-4 text-sm" style="color: #999">
252 - <div> 252 + <div class="flex min-w-20 mr-3">
253 <span> 253 <span>
254 {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }} 254 {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }}
255 </span> 255 </span>
256 <span v-if="item.viewType === ViewType.PUBLIC_VIEW"> 256 <span v-if="item.viewType === ViewType.PUBLIC_VIEW">
257 <Tooltip title="点击复制分享链接"> 257 <Tooltip title="点击复制分享链接">
258 - <ShareAltOutlined class="ml-2" @click.stop="handleCopyShareUrl(item)" /> 258 + <ShareAltOutlined class="ml-1" @click.stop="handleCopyShareUrl(item)" />
259 </Tooltip> 259 </Tooltip>
260 </span> 260 </span>
261 </div> 261 </div>
262 - <div>{{ item.createTime }}</div> 262 + <Tooltip placement="topLeft" :title="item.createTime">
  263 + <div class="truncate">{{ item.createTime }}</div>
  264 + </Tooltip>
263 </div> 265 </div>
264 </section> 266 </section>
265 </Card> 267 </Card>