Commit 60d4d0ee61c0ba9b1bcd8da9d7234da0dc6375fd

Authored by xp.Huang
2 parents b149ada3 ddb9a5ee

Merge branch 'perf/device-update' into 'main_dev'

perf: 优化编辑设备时变更产品与组织增加锁定提示

See merge request yunteng/thingskit-front!1116
  1 +<script setup lang="ts">
  2 + import { InputGroup, Button, Popconfirm } from 'ant-design-vue';
  3 + import { computed, ref, unref, watch } from 'vue';
  4 + import { componentMap } from '../componentMap';
  5 + import { ComponentType } from '../types';
  6 + import Icon from '/@/components/Icon';
  7 + import { PopConfirm } from '/@/components/Table';
  8 + import { upperFirst } from '/@/components/Transition/src/ExpandTransition';
  9 +
  10 + const props = withDefaults(
  11 + defineProps<{
  12 + value?: number | string | Recordable | Recordable[];
  13 + component?: ComponentType;
  14 + componentProps?: Recordable;
  15 + disabled?: boolean;
  16 + changeEvent?: string;
  17 + valueField?: string;
  18 + defaultLockStatus?: boolean;
  19 + popconfirm?: Omit<PopConfirm, 'confirm' | 'cancel'>;
  20 + }>(),
  21 + {
  22 + changeEvent: 'change',
  23 + valueField: 'value',
  24 + disabled: false,
  25 + component: 'Input',
  26 + defaultLockStatus: true,
  27 + componentProps: () => ({}),
  28 + }
  29 + );
  30 +
  31 + const slots = defineSlots<{
  32 + popconfirmTitle?: () => any;
  33 + }>();
  34 +
  35 + const emits = defineEmits(['change']);
  36 +
  37 + const ControlComponent = computed(() => componentMap.get(props.component));
  38 +
  39 + const getControlComponentProps = computed(() => {
  40 + const { componentProps, changeEvent, component, valueField, value } = props;
  41 + const isCheck = component && ['Switch', 'Checkbox'].includes(component);
  42 + const eventKey = `on${upperFirst(changeEvent)}`;
  43 + return {
  44 + ...componentProps,
  45 + [eventKey]: (...args: Nullable<Recordable>[]) => {
  46 + const [e] = args;
  47 + if (componentProps?.[eventKey]) {
  48 + componentProps?.[eventKey](...args);
  49 + }
  50 + const target = e ? e.target : null;
  51 + const value = target ? (isCheck ? target.checked : target.value) : e;
  52 + emits('change', value);
  53 + },
  54 + [valueField]: value,
  55 + };
  56 + });
  57 +
  58 + const popconfirmVisible = ref(false);
  59 +
  60 + const confirm = () => {
  61 + controlDisableStatus.value = false;
  62 + popconfirmVisible.value = false;
  63 + };
  64 +
  65 + const cancel = () => {
  66 + popconfirmVisible.value = false;
  67 + };
  68 +
  69 + const getPopConfirmProps = computed(() => {
  70 + const { popconfirm } = props;
  71 +
  72 + return {
  73 + ...popconfirm,
  74 + onConfirm: confirm,
  75 + onCancel: cancel,
  76 + onVisibleChange: () => {
  77 + if (!unref(controlDisableStatus)) {
  78 + popconfirmVisible.value = false;
  79 + controlDisableStatus.value = true;
  80 + return;
  81 + }
  82 + },
  83 + title: slots.popconfirmTitle ? slots.popconfirmTitle : popconfirm?.title,
  84 + };
  85 + });
  86 +
  87 + const controlDisableStatus = ref(props.defaultLockStatus);
  88 +
  89 + watch(
  90 + () => props.defaultLockStatus,
  91 + (target) => {
  92 + controlDisableStatus.value = !!target;
  93 + },
  94 + { immediate: true }
  95 + );
  96 +</script>
  97 +
  98 +<template>
  99 + <InputGroup compact class="w-full !flex">
  100 + <ControlComponent
  101 + v-bind="getControlComponentProps"
  102 + class="flex-auto"
  103 + :disabled="controlDisableStatus"
  104 + />
  105 + <Popconfirm v-model:visible="popconfirmVisible" v-bind="getPopConfirmProps">
  106 + <Button type="primary" :disabled="disabled">
  107 + <Icon
  108 + :icon="controlDisableStatus ? 'ant-design:lock-outlined' : 'ant-design:unlock-outlined'"
  109 + />
  110 + </Button>
  111 + </Popconfirm>
  112 + </InputGroup>
  113 +</template>
@@ -143,4 +143,5 @@ export type ComponentType = @@ -143,4 +143,5 @@ export type ComponentType =
143 | 'ConditionFilter' 143 | 'ConditionFilter'
144 | 'TimeRangePicker' 144 | 'TimeRangePicker'
145 | 'TriggerDurationInput' 145 | 'TriggerDurationInput'
146 - | 'AlarmProfileSelect'; 146 + | 'AlarmProfileSelect'
  147 + | 'LockControlGroup';
@@ -82,11 +82,11 @@ @@ -82,11 +82,11 @@
82 </script> 82 </script>
83 83
84 <template> 84 <template>
85 - <section class="flex">  
86 - <ApiTreeSelect v-bind="getBindProps" />  
87 - <Button v-if="getShowCreate" type="link" @click="handleOpenCreate" :disabled="disabled"  
88 - >新增组织</Button  
89 - > 85 + <section class="!flex">
  86 + <ApiTreeSelect v-bind="getBindProps" class="flex-auto" />
  87 + <Button v-if="getShowCreate" type="link" @click="handleOpenCreate" :disabled="disabled">
  88 + 新增组织
  89 + </Button>
90 <OrganizationDrawer v-if="getShowCreate" @register="registerDrawer" @success="handleReload" /> 90 <OrganizationDrawer v-if="getShowCreate" @register="registerDrawer" @success="handleReload" />
91 </section> 91 </section>
92 </template> 92 </template>
@@ -7,7 +7,7 @@ import { JSONEditor } from '/@/components/CodeEditor'; @@ -7,7 +7,7 @@ import { JSONEditor } from '/@/components/CodeEditor';
7 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; 7 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
8 import { getModelServices } from '/@/api/device/modelOfMatter'; 8 import { getModelServices } from '/@/api/device/modelOfMatter';
9 import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel'; 9 import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
10 -import { nextTick, toRaw, unref } from 'vue'; 10 +import { h, nextTick, toRaw, unref } from 'vue';
11 import ObjectModelValidateForm from '/@/components/Form/src/externalCompns/components/ObjectModelValidateForm/ObjectModelValidateForm.vue'; 11 import ObjectModelValidateForm from '/@/components/Form/src/externalCompns/components/ObjectModelValidateForm/ObjectModelValidateForm.vue';
12 import { CommandDeliveryWayEnum, ServiceCallTypeEnum } from '/@/enums/toolEnum'; 12 import { CommandDeliveryWayEnum, ServiceCallTypeEnum } from '/@/enums/toolEnum';
13 import { TaskTypeEnum } from '/@/views/task/center/config'; 13 import { TaskTypeEnum } from '/@/views/task/center/config';
@@ -18,9 +18,13 @@ import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter @@ -18,9 +18,13 @@ import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter
18 18
19 import { getOrganizationList } from '/@/api/system/system'; 19 import { getOrganizationList } from '/@/api/system/system';
20 import { copyTransFun } from '/@/utils/fnUtils'; 20 import { copyTransFun } from '/@/utils/fnUtils';
  21 +import LockControlGroup from '/@/components/Form/src/components/LockControlGroup.vue';
  22 +import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
21 23
22 useComponentRegister('JSONEditor', JSONEditor); 24 useComponentRegister('JSONEditor', JSONEditor);
23 useComponentRegister('ObjectModelValidateForm', ObjectModelValidateForm); 25 useComponentRegister('ObjectModelValidateForm', ObjectModelValidateForm);
  26 +useComponentRegister('LockControlGroup', LockControlGroup);
  27 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
24 28
25 export enum TypeEnum { 29 export enum TypeEnum {
26 IS_GATEWAY = 'GATEWAY', 30 IS_GATEWAY = 'GATEWAY',
@@ -30,6 +34,17 @@ export enum TypeEnum { @@ -30,6 +34,17 @@ export enum TypeEnum {
30 export const isGateWay = (type: string) => { 34 export const isGateWay = (type: string) => {
31 return type === TypeEnum.IS_GATEWAY; 35 return type === TypeEnum.IS_GATEWAY;
32 }; 36 };
  37 +
  38 +const updateProductHelpMessage = [
  39 + '注意:',
  40 + '修改设备产品时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"有关联设备,请自行调整并保存。',
  41 +];
  42 +
  43 +const updateOrgHelpMessage = [
  44 + '注意:',
  45 + '1、修改设备组织时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。',
  46 + '2、网关设备在修改组织时,其关联网关子设备组织归属于网关组织及以下不用修改,否则将同步更新网关子设备组织为网关组织。',
  47 +];
33 // 第一步的表单 48 // 第一步的表单
34 export const step1Schemas: FormSchema[] = [ 49 export const step1Schemas: FormSchema[] = [
35 { 50 {
@@ -105,45 +120,51 @@ export const step1Schemas: FormSchema[] = [ @@ -105,45 +120,51 @@ export const step1Schemas: FormSchema[] = [
105 field: 'profileId', 120 field: 'profileId',
106 label: '所属产品', 121 label: '所属产品',
107 required: true, 122 required: true,
108 - component: 'ApiSelect',  
109 - helpMessage: [  
110 - '注意:修改设备产品时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"有关联设备,请自行调整并保存。',  
111 - ], 123 + component: 'LockControlGroup',
  124 + helpMessage: updateProductHelpMessage,
  125 + renderComponentContent: () => ({
  126 + popconfirmTitle: () =>
  127 + updateProductHelpMessage.map((text) => h('div', { style: { maxWidth: '200px' } }, text)),
  128 + }),
112 componentProps: ({ formActionType, formModel }) => { 129 componentProps: ({ formActionType, formModel }) => {
113 const { setFieldsValue } = formActionType; 130 const { setFieldsValue } = formActionType;
114 return { 131 return {
115 - api: async () => {  
116 - const options = await queryDeviceProfileBy({  
117 - deviceType: formModel?.isUpdate ? formModel?.deviceType : null,  
118 - });  
119 - const { profileId } = formModel;  
120 - if (profileId) {  
121 - const selectRecord = options.find((item) => item.tbProfileId === profileId);  
122 - selectRecord && setFieldsValue({ transportType: selectRecord!.transportType });  
123 - }  
124 - return options;  
125 - },  
126 - labelField: 'name',  
127 - valueField: 'tbProfileId',  
128 - onChange(  
129 - _value: string,  
130 - option: { deviceType: string; transportType: string; id: string }  
131 - ) {  
132 - const { deviceType, transportType, id } = option;  
133 - setFieldsValue({  
134 - deviceType: deviceType,  
135 - transportType,  
136 - deviceProfileId: id,  
137 - gatewayId: null,  
138 - codeType: transportType === TransportTypeEnum.TCP ? TaskTypeEnum.MODBUS_RTU : null,  
139 - code: null,  
140 - addressCode: null,  
141 - }); 132 + component: 'ApiSelect',
  133 + defaultLockStatus: !!formModel?.isUpdate,
  134 + componentProps: {
  135 + api: async () => {
  136 + const options = await queryDeviceProfileBy({
  137 + deviceType: formModel?.isUpdate ? formModel?.deviceType : null,
  138 + });
  139 + const { profileId } = formModel;
  140 + if (profileId) {
  141 + const selectRecord = options.find((item) => item.tbProfileId === profileId);
  142 + selectRecord && setFieldsValue({ transportType: selectRecord!.transportType });
  143 + }
  144 + return options;
  145 + },
  146 + labelField: 'name',
  147 + valueField: 'tbProfileId',
  148 + onChange(
  149 + _value: string,
  150 + option: { deviceType: string; transportType: string; id: string }
  151 + ) {
  152 + const { deviceType, transportType, id } = option;
  153 + setFieldsValue({
  154 + deviceType: deviceType,
  155 + transportType,
  156 + deviceProfileId: id,
  157 + gatewayId: null,
  158 + codeType: transportType === TransportTypeEnum.TCP ? TaskTypeEnum.MODBUS_RTU : null,
  159 + code: null,
  160 + addressCode: null,
  161 + });
  162 + },
  163 + showSearch: true,
  164 + placeholder: '请选择产品',
  165 + filterOption: (inputValue: string, option: Record<'label' | 'value', string>) =>
  166 + option.label.includes(inputValue),
142 }, 167 },
143 - showSearch: true,  
144 - placeholder: '请选择产品',  
145 - filterOption: (inputValue: string, option: Record<'label' | 'value', string>) =>  
146 - option.label.includes(inputValue),  
147 }; 168 };
148 }, 169 },
149 }, 170 },
@@ -322,9 +343,19 @@ export const step1Schemas: FormSchema[] = [ @@ -322,9 +343,19 @@ export const step1Schemas: FormSchema[] = [
322 { 343 {
323 field: 'organizationId', 344 field: 'organizationId',
324 label: '所属组织', 345 label: '所属组织',
325 - component: 'Select', 346 + component: 'LockControlGroup',
326 required: true, 347 required: true,
327 - slot: 'addOrg', 348 + helpMessage: updateOrgHelpMessage,
  349 + renderComponentContent: () => ({
  350 + popconfirmTitle: () =>
  351 + updateOrgHelpMessage.map((text) => h('div', { style: { maxWidth: '240px' } }, text)),
  352 + }),
  353 + componentProps: ({ formModel }) => {
  354 + return {
  355 + component: 'OrgTreeSelect',
  356 + defaultLockStatus: !!formModel?.isUpdate,
  357 + };
  358 + },
328 }, 359 },
329 { 360 {
330 field: 'label', 361 field: 'label',
@@ -2,26 +2,6 @@ @@ -2,26 +2,6 @@
2 <div class="step1"> 2 <div class="step1">
3 <div class="step1-form"> 3 <div class="step1-form">
4 <BasicForm @register="register"> 4 <BasicForm @register="register">
5 - <template #addOrg="{ model, field }">  
6 - <div style="display: flex; align-items: center">  
7 - <div style="width: 245px">  
8 - <a-tree-select  
9 - @change="handleTreeOrg"  
10 - v-model:value="model[field]"  
11 - show-search  
12 - style="width: 100%"  
13 - :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"  
14 - placeholder="请选择组织"  
15 - allow-clear  
16 - tree-default-expand-all  
17 - :tree-data="model?.['organizationList'] || treeData"  
18 - />  
19 - </div>  
20 - <div>  
21 - <a-button type="link" @click="handleOpenOrgDrawer">新增组织</a-button>  
22 - </div>  
23 - </div>  
24 - </template>  
25 <template #snCode="{ model, field }"> 5 <template #snCode="{ model, field }">
26 <div class="flex"> 6 <div class="flex">
27 <Input :maxlength="36" v-model:value="model[field]" placeholder="请输入设备名称" /> 7 <Input :maxlength="36" v-model:value="model[field]" placeholder="请输入设备名称" />
@@ -91,11 +71,10 @@ @@ -91,11 +71,10 @@
91 </Spin> 71 </Spin>
92 </div> 72 </div>
93 </Modal> 73 </Modal>
94 - <DeptDrawer @register="registerModal" @success="handleSuccess" />  
95 </div> 74 </div>
96 </template> 75 </template>
97 <script lang="ts"> 76 <script lang="ts">
98 - import { defineComponent, ref, nextTick, unref, reactive, toRefs, onMounted } from 'vue'; 77 + import { defineComponent, ref, nextTick, unref, reactive } from 'vue';
99 import { BasicForm, useForm } from '/@/components/Form'; 78 import { BasicForm, useForm } from '/@/components/Form';
100 import { step1Schemas } from '../../config/data'; 79 import { step1Schemas } from '../../config/data';
101 import { useScript } from '/@/hooks/web/useScript'; 80 import { useScript } from '/@/hooks/web/useScript';
@@ -109,8 +88,6 @@ @@ -109,8 +88,6 @@
109 import { validatorLongitude, validatorLatitude } from '/@/utils/rules'; 88 import { validatorLongitude, validatorLatitude } from '/@/utils/rules';
110 import { getOrganizationList } from '/@/api/system/system'; 89 import { getOrganizationList } from '/@/api/system/system';
111 import { copyTransFun } from '/@/utils/fnUtils'; 90 import { copyTransFun } from '/@/utils/fnUtils';
112 - import { useDrawer } from '/@/components/Drawer';  
113 - import DeptDrawer from '/@/views/system/organization/OrganizationDrawer.vue';  
114 import { TaskTypeEnum } from '/@/views/task/center/config'; 91 import { TaskTypeEnum } from '/@/views/task/center/config';
115 import { toRaw } from 'vue'; 92 import { toRaw } from 'vue';
116 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; 93 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
@@ -129,7 +106,6 @@ @@ -129,7 +106,6 @@
129 FormItem: Form.Item, 106 FormItem: Form.Item,
130 Row, 107 Row,
131 Col, 108 Col,
132 - DeptDrawer,  
133 Spin, 109 Spin,
134 }, 110 },
135 props: { 111 props: {
@@ -152,16 +128,7 @@ @@ -152,16 +128,7 @@
152 copyTransFun(data as any as any[]); 128 copyTransFun(data as any as any[]);
153 orgData.treeData = data; 129 orgData.treeData = data;
154 }; 130 };
155 - onMounted(async () => {  
156 - await getOrganizationListFunc();  
157 - });  
158 - const { treeData } = toRefs(orgData);  
159 - const [registerModal, { openDrawer }] = useDrawer();  
160 - const handleOpenOrgDrawer = () => {  
161 - openDrawer(true, {  
162 - isUpdate: false,  
163 - });  
164 - }; 131 +
165 const handleSuccess = async () => { 132 const handleSuccess = async () => {
166 await getOrganizationListFunc(); 133 await getOrganizationListFunc();
167 }; 134 };
@@ -178,27 +145,17 @@ @@ -178,27 +145,17 @@
178 const devicePic = ref(''); 145 const devicePic = ref('');
179 const loading = ref(false); 146 const loading = ref(false);
180 147
181 - const [  
182 - register,  
183 - {  
184 - validate,  
185 - resetFields,  
186 - setFieldsValue,  
187 - getFieldsValue,  
188 - updateSchema,  
189 - clearValidate,  
190 - validateFields,  
191 - },  
192 - ] = useForm({  
193 - labelWidth: 140,  
194 - schemas: step1Schemas,  
195 - actionColOptions: {  
196 - span: 14,  
197 - },  
198 - labelAlign: 'right',  
199 - showResetButton: false,  
200 - showSubmitButton: false,  
201 - }); 148 + const [register, { validate, resetFields, setFieldsValue, getFieldsValue, updateSchema }] =
  149 + useForm({
  150 + labelWidth: 140,
  151 + schemas: step1Schemas,
  152 + actionColOptions: {
  153 + span: 14,
  154 + },
  155 + labelAlign: 'right',
  156 + showResetButton: false,
  157 + showSubmitButton: false,
  158 + });
202 159
203 async function nextStep() { 160 async function nextStep() {
204 try { 161 try {
@@ -503,13 +460,6 @@ @@ -503,13 +460,6 @@
503 }); 460 });
504 } 461 }
505 462
506 - const handleTreeOrg = (option: string) => {  
507 - if (option) clearValidate('organizationId');  
508 - else {  
509 - validateFields(['organizationId']);  
510 - }  
511 - };  
512 -  
513 return { 463 return {
514 resetFields, 464 resetFields,
515 positionState, 465 positionState,
@@ -538,11 +488,7 @@ @@ -538,11 +488,7 @@
538 loading, 488 loading,
539 rules, 489 rules,
540 redirectPosition, 490 redirectPosition,
541 - treeData,  
542 - registerModal,  
543 - handleOpenOrgDrawer,  
544 handleSuccess, 491 handleSuccess,
545 - handleTreeOrg,  
546 devicePositionState, 492 devicePositionState,
547 spinning, 493 spinning,
548 }; 494 };