Commit eca2a6378984c21144ac34954df08eb64e67f373

Authored by ww
1 parent ef367005

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

1 1 <script lang="ts">
2 2 export default {
  3 + components: { Spin },
3 4 inheritAttrs: false,
4 5 };
5 6 </script>
6 7 <script lang="ts" setup>
  8 + import { Spin } from 'ant-design-vue';
7 9 import { RadioRecord } from '../../detail/config/util';
8 10 import { ControlComponentDefaultConfig, ControlComponentValue } from './control.config';
9 11 import { useSendCommand } from './useSendCommand';
  12 + import { ref } from 'vue';
10 13
11 14 interface VisualComponentProps<Layout = Recordable, Value = ControlComponentValue> {
12 15 value?: Value;
... ... @@ -23,33 +26,45 @@
23 26 const emit = defineEmits(['update:value', 'change']);
24 27
25 28 const { sendCommand } = useSendCommand();
26   - const handleChange = (event: Event) => {
  29 +
  30 + const loading = ref(false);
  31 + const handleChange = async (event: Event) => {
27 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 42 emit('update:value', _value);
29 43 emit('change', _value);
30   - sendCommand(props.value!, _value);
31 44 };
32 45 </script>
33 46
34 47 <template>
35 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 68 </div>
54 69 </template>
55 70
... ...
... ... @@ -31,8 +31,14 @@
31 31 const checked = ref(!!Number(props.value.value));
32 32
33 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 44 watchEffect(() => {
... ... @@ -59,6 +65,6 @@
59 65 {{ props.value.attributeRename || props.value.attribute }}
60 66 </span>
61 67 </div>
62   - <Switch v-model:checked="checked" @change="handleChange" />
  68 + <Switch v-model:checked="checked" :loading="loading" @change="handleChange" />
63 69 </div>
64 70 </template>
... ...
1 1 <script lang="ts">
2 2 export default {
  3 + components: { Spin },
3 4 inheritAttrs: false,
4 5 };
5 6 </script>
... ... @@ -8,6 +9,8 @@
8 9 import { DEFAULT_RADIO_RECORD, fontSize, RadioRecord } from '../../detail/config/util';
9 10 import { ControlComponentDefaultConfig, ControlComponentValue } from './control.config';
10 11 import { useSendCommand } from './useSendCommand';
  12 + import { ref } from 'vue';
  13 + import { Spin } from 'ant-design-vue';
11 14
12 15 const props = defineProps<{
13 16 value?: ControlComponentValue;
... ... @@ -18,11 +21,20 @@
18 21 const emit = defineEmits(['update:value', 'change']);
19 22
20 23 const { sendCommand } = useSendCommand();
21   - const handleChange = (event: Event) => {
  24 + const loading = ref(false);
  25 + const handleChange = async (event: Event) => {
22 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 36 emit('update:value', _value);
24 37 emit('change', _value);
25   - sendCommand(props.value!, _value);
26 38 };
27 39
28 40 const getRadio = computed(() => {
... ... @@ -32,35 +44,37 @@
32 44
33 45 <template>
34 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 78 </div>
65 79 </template>
66 80
... ...
... ... @@ -5,33 +5,44 @@ import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
5 5 import { getModelServices } from '/@/api/device/modelOfMatter';
6 6 import { useMessage } from '/@/hooks/web/useMessage';
7 7 import { isString } from '/@/utils/is';
  8 +import { TransportTypeEnum } from '/@/views/device/profiles/components/TransportDescript/const';
8 9
9 10 const { createMessage } = useMessage();
10 11 export function useSendCommand() {
  12 + const error = () => {
  13 + createMessage.error('下发指令失败');
  14 + return false;
  15 + };
11 16 const sendCommand = async (record: ControlComponentValue, value: any) => {
12   - if (!record) return;
  17 + if (!record) return error();
13 18 const { deviceProfileId, attribute, deviceType } = record;
14 19 let { deviceId } = record;
15   - if (!deviceId) return;
  20 + if (!deviceId) return error();
16 21 try {
17 22 const list = await getDeviceProfile();
18 23 const deviceProfile = list.find((item) => item.id === deviceProfileId);
19   - if (!deviceProfile) return;
  24 + if (!deviceProfile) return error();
  25 +
20 26 let params: string | Recordable = {
21 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 33 const record = serviceList.find((item) => item.identifier === attribute);
26 34 const sendCommand = record?.functionJson.inputData?.at(0)?.serviceCommand || '';
27 35 params = isString(sendCommand) ? sendCommand : JSON.stringify(sendCommand);
28 36 }
  37 +
29 38 if (deviceType === DeviceTypeEnum.SENSOR) {
30 39 deviceId = await getDeviceRelation({
31 40 deviceId,
32 41 isSlave: deviceType === DeviceTypeEnum.SENSOR,
33 42 });
34 43 }
  44 +
  45 + // 控制按钮下发命令为0 或 1
35 46 await sendCommandOneway({
36 47 deviceId,
37 48 value: {
... ... @@ -44,7 +55,11 @@ export function useSendCommand() {
44 55 },
45 56 });
46 57 createMessage.success('命令下发成功');
47   - } catch (error) {}
  58 + } catch (msg) {
  59 + return error();
  60 + } finally {
  61 + return true;
  62 + }
48 63 };
49 64 return {
50 65 sendCommand,
... ...
... ... @@ -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 67 const validate = async () => {
61 68 await basicMethod.validate();
62 69 await validateDataSourceField();
... ... @@ -258,6 +265,16 @@
258 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 278 onMounted(() => handleSort());
262 279
263 280 defineExpose({
... ... @@ -293,7 +310,7 @@
293 310
294 311 <section ref="formListEl">
295 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 314 <div class="pl-2 flex-auto">
298 315 <component
299 316 :frontId="$props.frontId"
... ...
... ... @@ -9,6 +9,8 @@ import { getModelServices } from '/@/api/device/modelOfMatter';
9 9 import { findDictItemByCode } from '/@/api/system/dict';
10 10 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
11 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 15 export enum BasicConfigField {
14 16 NAME = 'name',
... ... @@ -66,7 +68,7 @@ export const isMapComponent = (frontId: FrontComponent) => {
66 68 };
67 69
68 70 const isTcpProfile = (transportType: string) => {
69   - return transportType === 'TCP';
  71 + return transportType === TransportTypeEnum.TCP;
70 72 };
71 73
72 74 export const basicSchema: FormSchema[] = [
... ... @@ -117,6 +119,7 @@ export const dataSourceSchema = (isEdit: boolean, frontId?: FrontComponent): For
117 119 component: 'ApiSelect',
118 120 label: '设备类型',
119 121 colProps: { span: 8 },
  122 + rules: [{ message: '请选择设备类型', required: true }],
120 123 // defaultValue: DeviceTypeEnum.SENSOR,
121 124 componentProps: ({ formActionType }) => {
122 125 const { setFieldsValue } = formActionType;
... ... @@ -180,7 +183,7 @@ export const dataSourceSchema = (isEdit: boolean, frontId?: FrontComponent): For
180 183 colProps: { span: 8 },
181 184 rules: [{ required: true, message: '组织为必填项' }],
182 185 componentProps({ formActionType }) {
183   - const { setFieldsValue } = formActionType;
  186 + const { setFieldsValue, getFieldsValue } = formActionType;
184 187 return {
185 188 placeholder: '请选择组织',
186 189 api: async () => {
... ... @@ -192,6 +195,9 @@ export const dataSourceSchema = (isEdit: boolean, frontId?: FrontComponent): For
192 195 setFieldsValue({
193 196 [DataSourceField.DEVICE_ID]: null,
194 197 });
  198 + nextTick(() => {
  199 + console.log('org change', getFieldsValue());
  200 + });
195 201 },
196 202 getPopupContainer: () => document.body,
197 203 };
... ... @@ -250,11 +256,24 @@ export const dataSourceSchema = (isEdit: boolean, frontId?: FrontComponent): For
250 256 component: 'ApiSelect',
251 257 label: '属性',
252 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 272 componentProps({ formModel }) {
255 273 const deviceProfileId = formModel[DataSourceField.DEVICE_PROFILE_ID];
256 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 277 return {
259 278 api: async () => {
260 279 try {
... ...
... ... @@ -47,7 +47,7 @@
47 47 import { BAI_DU_MAP_GL_LIB, BAI_DU_MAP_TRACK_ANIMATION } from '/@/utils/fnUtils';
48 48
49 49 const props = defineProps<{
50   - value: Recordable;
  50 + value?: Recordable;
51 51 }>();
52 52
53 53 const ROUTE = useRoute();
... ...
... ... @@ -126,7 +126,7 @@ export function useSocketConnect(dataSourceRef: Ref<DataBoardLayoutInfo[]>) {
126 126 const { subscriptionId, data = {} } = res;
127 127 if (isNullAndUnDef(subscriptionId)) return;
128 128 const mappingRecord = cmdIdMapping.get(subscriptionId);
129   - if (!mappingRecord) return;
  129 + if (!mappingRecord || !data) return;
130 130 mappingRecord.forEach((item) => {
131 131 const { attribute, recordIndex, dataSourceIndex } = item;
132 132 const [[timespan, value]] = data[attribute];
... ...
... ... @@ -249,17 +249,19 @@
249 249 </div>
250 250 </div>
251 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 253 <span>
254 254 {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }}
255 255 </span>
256 256 <span v-if="item.viewType === ViewType.PUBLIC_VIEW">
257 257 <Tooltip title="点击复制分享链接">
258   - <ShareAltOutlined class="ml-2" @click.stop="handleCopyShareUrl(item)" />
  258 + <ShareAltOutlined class="ml-1" @click.stop="handleCopyShareUrl(item)" />
259 259 </Tooltip>
260 260 </span>
261 261 </div>
262   - <div>{{ item.createTime }}</div>
  262 + <Tooltip placement="topLeft" :title="item.createTime">
  263 + <div class="truncate">{{ item.createTime }}</div>
  264 + </Tooltip>
263 265 </div>
264 266 </section>
265 267 </Card>
... ...