Commit ddb9a5ee0f32212590292892745ce9a4804afa20

Authored by ww
1 parent b149ada3

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

  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 143 | 'ConditionFilter'
144 144 | 'TimeRangePicker'
145 145 | 'TriggerDurationInput'
146   - | 'AlarmProfileSelect';
  146 + | 'AlarmProfileSelect'
  147 + | 'LockControlGroup';
... ...
... ... @@ -82,11 +82,11 @@
82 82 </script>
83 83
84 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 90 <OrganizationDrawer v-if="getShowCreate" @register="registerDrawer" @success="handleReload" />
91 91 </section>
92 92 </template>
... ...
... ... @@ -7,7 +7,7 @@ import { JSONEditor } from '/@/components/CodeEditor';
7 7 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
8 8 import { getModelServices } from '/@/api/device/modelOfMatter';
9 9 import { ModelOfMatterParams } from '/@/api/device/model/modelOfMatterModel';
10   -import { nextTick, toRaw, unref } from 'vue';
  10 +import { h, nextTick, toRaw, unref } from 'vue';
11 11 import ObjectModelValidateForm from '/@/components/Form/src/externalCompns/components/ObjectModelValidateForm/ObjectModelValidateForm.vue';
12 12 import { CommandDeliveryWayEnum, ServiceCallTypeEnum } from '/@/enums/toolEnum';
13 13 import { TaskTypeEnum } from '/@/views/task/center/config';
... ... @@ -18,9 +18,13 @@ import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter
18 18
19 19 import { getOrganizationList } from '/@/api/system/system';
20 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 24 useComponentRegister('JSONEditor', JSONEditor);
23 25 useComponentRegister('ObjectModelValidateForm', ObjectModelValidateForm);
  26 +useComponentRegister('LockControlGroup', LockControlGroup);
  27 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
24 28
25 29 export enum TypeEnum {
26 30 IS_GATEWAY = 'GATEWAY',
... ... @@ -30,6 +34,17 @@ export enum TypeEnum {
30 34 export const isGateWay = (type: string) => {
31 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 49 export const step1Schemas: FormSchema[] = [
35 50 {
... ... @@ -105,45 +120,51 @@ export const step1Schemas: FormSchema[] = [
105 120 field: 'profileId',
106 121 label: '所属产品',
107 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 129 componentProps: ({ formActionType, formModel }) => {
113 130 const { setFieldsValue } = formActionType;
114 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 343 {
323 344 field: 'organizationId',
324 345 label: '所属组织',
325   - component: 'Select',
  346 + component: 'LockControlGroup',
326 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 361 field: 'label',
... ...
... ... @@ -2,26 +2,6 @@
2 2 <div class="step1">
3 3 <div class="step1-form">
4 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 5 <template #snCode="{ model, field }">
26 6 <div class="flex">
27 7 <Input :maxlength="36" v-model:value="model[field]" placeholder="请输入设备名称" />
... ... @@ -91,11 +71,10 @@
91 71 </Spin>
92 72 </div>
93 73 </Modal>
94   - <DeptDrawer @register="registerModal" @success="handleSuccess" />
95 74 </div>
96 75 </template>
97 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 78 import { BasicForm, useForm } from '/@/components/Form';
100 79 import { step1Schemas } from '../../config/data';
101 80 import { useScript } from '/@/hooks/web/useScript';
... ... @@ -109,8 +88,6 @@
109 88 import { validatorLongitude, validatorLatitude } from '/@/utils/rules';
110 89 import { getOrganizationList } from '/@/api/system/system';
111 90 import { copyTransFun } from '/@/utils/fnUtils';
112   - import { useDrawer } from '/@/components/Drawer';
113   - import DeptDrawer from '/@/views/system/organization/OrganizationDrawer.vue';
114 91 import { TaskTypeEnum } from '/@/views/task/center/config';
115 92 import { toRaw } from 'vue';
116 93 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
... ... @@ -129,7 +106,6 @@
129 106 FormItem: Form.Item,
130 107 Row,
131 108 Col,
132   - DeptDrawer,
133 109 Spin,
134 110 },
135 111 props: {
... ... @@ -152,16 +128,7 @@
152 128 copyTransFun(data as any as any[]);
153 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 132 const handleSuccess = async () => {
166 133 await getOrganizationListFunc();
167 134 };
... ... @@ -178,27 +145,17 @@
178 145 const devicePic = ref('');
179 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 160 async function nextStep() {
204 161 try {
... ... @@ -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 463 return {
514 464 resetFields,
515 465 positionState,
... ... @@ -538,11 +488,7 @@
538 488 loading,
539 489 rules,
540 490 redirectPosition,
541   - treeData,
542   - registerModal,
543   - handleOpenOrgDrawer,
544 491 handleSuccess,
545   - handleTreeOrg,
546 492 devicePositionState,
547 493 spinning,
548 494 };
... ...