Commit caebda75e84163864a752cc5cc595f33f6d177d3

Authored by dev001
1 parent 5d45f035

feat: 设备列表:批量操作新增批量公开、批量私有、批量修改组织

@@ -57,6 +57,21 @@ enum DeviceManagerApi { @@ -57,6 +57,21 @@ enum DeviceManagerApi {
57 * @description 批量变更产品 57 * @description 批量变更产品
58 */ 58 */
59 BATCH_UPDATE_PRODUCT = '/device/batch/update', 59 BATCH_UPDATE_PRODUCT = '/device/batch/update',
  60 +
  61 + /**
  62 + * @description 批量公开设备
  63 + */
  64 + BATCH_PUBLIC_DEVICE = '/batch/customer/public/device/',
  65 +
  66 + /**
  67 + * @description 批量私有设备
  68 + */
  69 + BATCH_PRIVATE_DEVICE = '/batch/customer/device/',
  70 +
  71 + /**
  72 + * @description 批量修改设备
  73 + */
  74 + BATCH_UPDATE_DEVICE = '/device/batch/update',
60 } 75 }
61 76
62 export const devicePage = (params: DeviceQueryParam) => { 77 export const devicePage = (params: DeviceQueryParam) => {
@@ -398,3 +413,43 @@ export const doBatchUpdateProduct = (params: { deviceIds: string[]; deviceProfil @@ -398,3 +413,43 @@ export const doBatchUpdateProduct = (params: { deviceIds: string[]; deviceProfil
398 { joinPrefix: false } 413 { joinPrefix: false }
399 ); 414 );
400 }; 415 };
  416 +
  417 +/**
  418 + *
  419 + * @param deviceIds tbDeviceId分割的字符串
  420 + * @returns
  421 + */
  422 +export const doBatchPublicDevice = (deviceIds: string) => {
  423 + return defHttp.post(
  424 + {
  425 + url: `${DeviceManagerApi.BATCH_PUBLIC_DEVICE}?deviceIds=${deviceIds}`,
  426 + },
  427 + { joinPrefix: false }
  428 + );
  429 +};
  430 +
  431 +/**
  432 + *
  433 + * @param deviceIds tbDeviceId分割的字符串
  434 + * @returns
  435 + */
  436 +export const doBatchPrivateDevice = (deviceIds: string) => {
  437 + return defHttp.post(
  438 + {
  439 + url: `${DeviceManagerApi.BATCH_PRIVATE_DEVICE}?deviceIds=${deviceIds}`,
  440 + },
  441 + { joinPrefix: false }
  442 + );
  443 +};
  444 +
  445 +/**
  446 + *
  447 + * @param orgId
  448 + * @returns
  449 + */
  450 +export const doBatchUpdateDevice = (orgId: string, data: Recordable[]) => {
  451 + return defHttp.post({
  452 + url: `${DeviceManagerApi.BATCH_UPDATE_DEVICE}?orgId=${orgId}`,
  453 + data,
  454 + });
  455 +};
@@ -53,6 +53,7 @@ export interface DeviceModel { @@ -53,6 +53,7 @@ export interface DeviceModel {
53 customerId?: string; 53 customerId?: string;
54 alias?: string; 54 alias?: string;
55 tbDeviceId?: string; 55 tbDeviceId?: string;
  56 + customerName?: string;
56 } 57 }
57 58
58 export interface DeviceProfileModel { 59 export interface DeviceProfileModel {
@@ -207,6 +208,8 @@ export interface DeviceRecord { @@ -207,6 +208,8 @@ export interface DeviceRecord {
207 manufacturer: string; 208 manufacturer: string;
208 streamMode: string; 209 streamMode: string;
209 }; 210 };
  211 + customerName?: string;
  212 + customerId?: string;
210 } 213 }
211 214
212 export interface DeviceModelOfMatterAttrs { 215 export interface DeviceModelOfMatterAttrs {
@@ -36,7 +36,7 @@ const updateProductHelpMessage = [ @@ -36,7 +36,7 @@ const updateProductHelpMessage = [
36 '修改设备产品时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。', 36 '修改设备产品时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。',
37 ]; 37 ];
38 38
39 -const updateOrgHelpMessage = [ 39 +export const updateOrgHelpMessage = [
40 '注意:', 40 '注意:',
41 '1、修改设备组织时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。', 41 '1、修改设备组织时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。',
42 '2、网关设备在修改组织时,其关联网关子设备组织归属于网关组织及以下不用修改,否则将同步更新网关子设备组织为网关组织。', 42 '2、网关设备在修改组织时,其关联网关子设备组织归属于网关组织及以下不用修改,否则将同步更新网关子设备组织为网关组织。',
@@ -314,6 +314,12 @@ export const step1Schemas: FormSchema[] = [ @@ -314,6 +314,12 @@ export const step1Schemas: FormSchema[] = [
314 ifShow: false, 314 ifShow: false,
315 }, 315 },
316 { 316 {
  317 + field: 'customerId',
  318 + label: '用来判断编辑时禁用组织修改',
  319 + component: 'Input',
  320 + ifShow: false,
  321 + },
  322 + {
317 field: 'organizationId', 323 field: 'organizationId',
318 label: '所属组织', 324 label: '所属组织',
319 component: 'LockControlGroup', 325 component: 'LockControlGroup',
@@ -327,6 +333,7 @@ export const step1Schemas: FormSchema[] = [ @@ -327,6 +333,7 @@ export const step1Schemas: FormSchema[] = [
327 return { 333 return {
328 component: 'OrgTreeSelect', 334 component: 'OrgTreeSelect',
329 defaultLockStatus: !!formModel?.isUpdate, 335 defaultLockStatus: !!formModel?.isUpdate,
  336 + disabled: !!formModel?.customerId,
330 componentProps: { 337 componentProps: {
331 apiTreeSelectProps: { 338 apiTreeSelectProps: {
332 params: { 339 params: {
1 import { formatToDateTime } from '/@/utils/dateUtil'; 1 import { formatToDateTime } from '/@/utils/dateUtil';
2 -import { FormSchema } from '/@/components/Form'; 2 +import { FormSchema, useComponentRegister } from '/@/components/Form';
3 import { BasicColumn } from '/@/components/Table'; 3 import { BasicColumn } from '/@/components/Table';
4 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel'; 4 import { DeviceTypeEnum } from '/@/api/device/model/deviceModel';
5 import { getCustomerList } from '/@/api/device/deviceManager'; 5 import { getCustomerList } from '/@/api/device/deviceManager';
@@ -7,12 +7,15 @@ import { DescItem } from '/@/components/Description/index'; @@ -7,12 +7,15 @@ import { DescItem } from '/@/components/Description/index';
7 import moment from 'moment'; 7 import moment from 'moment';
8 import { CSSProperties, h } from 'vue'; 8 import { CSSProperties, h } from 'vue';
9 import { Button, Tooltip } from 'ant-design-vue'; 9 import { Button, Tooltip } from 'ant-design-vue';
10 -import { TypeEnum } from './data'; 10 +import { TypeEnum, updateOrgHelpMessage } from './data';
11 import { PageEnum } from '/@/enums/pageEnum'; 11 import { PageEnum } from '/@/enums/pageEnum';
12 import { useGo } from '/@/hooks/web/usePage'; 12 import { useGo } from '/@/hooks/web/usePage';
13 import { useAuthDeviceDetail } from '../hook/useAuthDeviceDetail'; 13 import { useAuthDeviceDetail } from '../hook/useAuthDeviceDetail';
14 import { findDictItemByCode } from '/@/api/system/dict'; 14 import { findDictItemByCode } from '/@/api/system/dict';
15 import { isNullOrUnDef } from '/@/utils/is'; 15 import { isNullOrUnDef } from '/@/utils/is';
  16 +import { OrgTreeSelect } from '/@/views/common/OrgTreeSelect';
  17 +
  18 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
16 19
17 // 设备详情的描述 20 // 设备详情的描述
18 export const descSchema = (emit: EmitType): DescItem[] => { 21 export const descSchema = (emit: EmitType): DescItem[] => {
@@ -407,3 +410,16 @@ export const customerForm: FormSchema[] = [ @@ -407,3 +410,16 @@ export const customerForm: FormSchema[] = [
407 colProps: { span: 12 }, 410 colProps: { span: 12 },
408 }, 411 },
409 ]; 412 ];
  413 +
  414 +export const orgForm: FormSchema[] = [
  415 + {
  416 + field: 'organizationId',
  417 + component: 'OrgTreeSelect',
  418 + helpMessage: updateOrgHelpMessage,
  419 + label: '选择组织',
  420 + required: true,
  421 + componentProps: {
  422 + placeholder: '请选择',
  423 + },
  424 + },
  425 +];
  1 +<template>
  2 + <BasicModal
  3 + v-bind="$attrs"
  4 + @register="registerModal"
  5 + title="批量修改设备"
  6 + :canFullscreen="false"
  7 + centered
  8 + @ok="dispatchCustomer"
  9 + @cancel="resetFields"
  10 + :minHeight="300"
  11 + okText="确认"
  12 + >
  13 + <BasicForm @register="registerForm" />
  14 + </BasicModal>
  15 +</template>
  16 +
  17 +<script lang="ts">
  18 + import { defineComponent, ref } from 'vue';
  19 + import { BasicModal, useModalInner } from '/@/components/Modal';
  20 + import { BasicForm, useForm } from '/@/components/Form';
  21 + import { orgForm } from '../../config/detail.config';
  22 + import { doBatchUpdateDevice } from '/@/api/device/deviceManager';
  23 + import { useMessage } from '/@/hooks/web/useMessage';
  24 +
  25 + export default defineComponent({
  26 + name: 'AlarmDetailModal',
  27 + components: {
  28 + BasicModal,
  29 + BasicForm,
  30 + },
  31 + emits: ['reload', 'register'],
  32 + setup(_, { emit }) {
  33 + const { createMessage } = useMessage();
  34 +
  35 + const record = ref([]);
  36 +
  37 + const [registerModal, { closeModal }] = useModalInner((data) => {
  38 + record.value = data;
  39 + });
  40 +
  41 + const [registerForm, { validate, resetFields }] = useForm({
  42 + labelWidth: 100,
  43 + showActionButtonGroup: false,
  44 + schemas: orgForm,
  45 + });
  46 +
  47 + // 分配组织
  48 + const dispatchCustomer = async () => {
  49 + const value = await validate();
  50 + if (!value) return;
  51 + await doBatchUpdateDevice(value.organizationId, record.value);
  52 + closeModal();
  53 + resetFields();
  54 + emit('reload');
  55 + createMessage.success('操作成功');
  56 + };
  57 +
  58 + return {
  59 + registerModal,
  60 + registerForm,
  61 + dispatchCustomer,
  62 + resetFields,
  63 + };
  64 + },
  65 + });
  66 +</script>
@@ -418,6 +418,7 @@ @@ -418,6 +418,7 @@
418 code: data?.code, 418 code: data?.code,
419 addressCode: data?.code, 419 addressCode: data?.code,
420 isUpdate: unref(isUpdate1), 420 isUpdate: unref(isUpdate1),
  421 + customerId: data?.customerId,
421 }); 422 });
422 } 423 }
423 // 父组件调用获取字段值的方法 424 // 父组件调用获取字段值的方法
@@ -23,13 +23,14 @@ @@ -23,13 +23,14 @@
23 > 23 >
24 <AuthDropDown 24 <AuthDropDown
25 v-if="authBtn(role)" 25 v-if="authBtn(role)"
26 - :disabled="!isExistOption" 26 + :disabled="isPublicAndPrivateFlag || !isExistOption"
27 :dropMenuList="[ 27 :dropMenuList="[
28 { 28 {
29 text: '删除设备', 29 text: '删除设备',
30 auth: DeviceListAuthEnum.DELETE, 30 auth: DeviceListAuthEnum.DELETE,
31 icon: 'ant-design:delete-outlined', 31 icon: 'ant-design:delete-outlined',
32 event: '', 32 event: '',
  33 + disabled: !batchPrivateFlag,
33 popconfirm: { 34 popconfirm: {
34 title: '您确定要批量删除数据', 35 title: '您确定要批量删除数据',
35 onConfirm: () => handleDelete(), 36 onConfirm: () => handleDelete(),
@@ -40,6 +41,7 @@ @@ -40,6 +41,7 @@
40 auth: DeviceListAuthEnum.ASSIGN, 41 auth: DeviceListAuthEnum.ASSIGN,
41 icon: 'mdi:account-arrow-left', 42 icon: 'mdi:account-arrow-left',
42 event: '', 43 event: '',
  44 + disabled: !batchPrivateFlag,
43 onClick: handleBatchAssign.bind(null), 45 onClick: handleBatchAssign.bind(null),
44 }, 46 },
45 { 47 {
@@ -47,12 +49,35 @@ @@ -47,12 +49,35 @@
47 auth: DeviceListAuthEnum.UPDATE_PRODUCT, 49 auth: DeviceListAuthEnum.UPDATE_PRODUCT,
48 icon: 'clarity:note-edit-line', 50 icon: 'clarity:note-edit-line',
49 event: '', 51 event: '',
50 - disabled: batchUpdateProductFlag, 52 + disabled: !batchPrivateFlag || batchUpdateProductFlag,
51 onClick: handelOpenBatchUpdateProductModal, 53 onClick: handelOpenBatchUpdateProductModal,
52 }, 54 },
  55 + {
  56 + text: '批量公开',
  57 + icon: 'ant-design:wallet-outlined',
  58 + event: '',
  59 + disabled: !batchPrivateFlag,
  60 + onClick: handleBatchPublic.bind(null),
  61 + },
  62 + {
  63 + text: '批量私有',
  64 + icon: 'ant-design:wallet-outlined',
  65 + event: '',
  66 + disabled: batchPrivateFlag,
  67 + onClick: handleBatchPrivate.bind(null),
  68 + },
  69 + {
  70 + text: '批量修改组织',
  71 + icon: 'ant-design:wallet-outlined',
  72 + event: '',
  73 + disabled: !batchPrivateFlag,
  74 + onClick: handleBatchOrg.bind(null),
  75 + },
53 ]" 76 ]"
54 > 77 >
55 - <Button type="primary" :disabled="!isExistOption">批量操作</Button> 78 + <Button type="primary" :disabled="isPublicAndPrivateFlag || !isExistOption"
  79 + >批量操作</Button
  80 + >
56 </AuthDropDown> 81 </AuthDropDown>
57 </Authority> 82 </Authority>
58 </template> 83 </template>
@@ -225,6 +250,7 @@ @@ -225,6 +250,7 @@
225 250
226 <DeviceModal @register="registerModal" @success="handleSuccess" @reload="handleSuccess" /> 251 <DeviceModal @register="registerModal" @success="handleSuccess" @reload="handleSuccess" />
227 <CustomerModal @register="registerCustomerModal" @reload="handleReload" /> 252 <CustomerModal @register="registerCustomerModal" @reload="handleReload" />
  253 + <OrgModal @register="registerOrgModal" @reload="handleReload" />
228 254
229 <BatchImportModal @register="registerImportModal" @import-finally="handleImportFinally" /> 255 <BatchImportModal @register="registerImportModal" @import-finally="handleImportFinally" />
230 256
@@ -254,6 +280,8 @@ @@ -254,6 +280,8 @@
254 getGATEWAY, 280 getGATEWAY,
255 privateDevice, 281 privateDevice,
256 publicDevice, 282 publicDevice,
  283 + doBatchPublicDevice,
  284 + doBatchPrivateDevice,
257 } from '/@/api/device/deviceManager'; 285 } from '/@/api/device/deviceManager';
258 import { PageEnum } from '/@/enums/pageEnum'; 286 import { PageEnum } from '/@/enums/pageEnum';
259 import { useGo } from '/@/hooks/web/usePage'; 287 import { useGo } from '/@/hooks/web/usePage';
@@ -264,6 +292,7 @@ @@ -264,6 +292,7 @@
264 import { useDrawer } from '/@/components/Drawer'; 292 import { useDrawer } from '/@/components/Drawer';
265 import DeviceDetailDrawer from './cpns/modal/DeviceDetailDrawer.vue'; 293 import DeviceDetailDrawer from './cpns/modal/DeviceDetailDrawer.vue';
266 import CustomerModal from './cpns/modal/CustomerModal.vue'; 294 import CustomerModal from './cpns/modal/CustomerModal.vue';
  295 + import OrgModal from './cpns/modal/OrgModal.vue';
267 import BatchImportModal from './cpns/modal/BatchImportModal/index.vue'; 296 import BatchImportModal from './cpns/modal/BatchImportModal/index.vue';
268 import { useMessage } from '/@/hooks/web/useMessage'; 297 import { useMessage } from '/@/hooks/web/useMessage';
269 import { USER_INFO_KEY } from '/@/enums/cacheEnum'; 298 import { USER_INFO_KEY } from '/@/enums/cacheEnum';
@@ -292,6 +321,7 @@ @@ -292,6 +321,7 @@
292 const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo); 321 const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
293 const [registerModal, { openModal }] = useModal(); 322 const [registerModal, { openModal }] = useModal();
294 const [registerCustomerModal, { openModal: openCustomerModal }] = useModal(); 323 const [registerCustomerModal, { openModal: openCustomerModal }] = useModal();
  324 + const [registerOrgModal, { openModal: openOrgodal }] = useModal();
295 const [registerDetailDrawer, { openDrawer }] = useDrawer(); 325 const [registerDetailDrawer, { openDrawer }] = useDrawer();
296 const [registerTbDetailDrawer, { openDrawer: openTbDeviceDrawer }] = useDrawer(); 326 const [registerTbDetailDrawer, { openDrawer: openTbDeviceDrawer }] = useDrawer();
297 const [registerGatewayDetailDrawer, { openDrawer: openGatewayDetailDrawer }] = useDrawer(); 327 const [registerGatewayDetailDrawer, { openDrawer: openGatewayDetailDrawer }] = useDrawer();
@@ -326,6 +356,10 @@ @@ -326,6 +356,10 @@
326 356
327 const batchUpdateProductFlag = ref(true); 357 const batchUpdateProductFlag = ref(true);
328 358
  359 + const isPublicAndPrivateFlag = ref(false);
  360 +
  361 + const batchPrivateFlag = ref(true);
  362 +
329 const [ 363 const [
330 registerTable, 364 registerTable,
331 { 365 {
@@ -376,7 +410,7 @@ @@ -376,7 +410,7 @@
376 rowSelection: { 410 rowSelection: {
377 type: 'checkbox', 411 type: 'checkbox',
378 getCheckboxProps: (record: DeviceModel) => { 412 getCheckboxProps: (record: DeviceModel) => {
379 - return { disabled: !!record.customerId }; 413 + return { disabled: !!record.customerId && record.customerName !== 'Public' };
380 }, 414 },
381 onSelect(_record, _selected, selectedRows) { 415 onSelect(_record, _selected, selectedRows) {
382 const [firstItem] = selectedRows as DeviceRecord[]; 416 const [firstItem] = selectedRows as DeviceRecord[];
@@ -384,6 +418,11 @@ @@ -384,6 +418,11 @@
384 batchUpdateProductFlag.value = 418 batchUpdateProductFlag.value =
385 !selectedRows.length || 419 !selectedRows.length ||
386 !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType); 420 !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType);
  421 +
  422 + batchPrivateFlag.value = selectedRows.some((item: DeviceRecord) => !item?.customerId);
  423 + isPublicAndPrivateFlag.value =
  424 + selectedRows.some((item: DeviceRecord) => !item?.customerId) &&
  425 + selectedRows.some((item: DeviceRecord) => item?.customerAdditionalInfo?.isPublic);
387 }, 426 },
388 onSelectAll(_selected, selectedRows) { 427 onSelectAll(_selected, selectedRows) {
389 const [firstItem] = selectedRows as DeviceRecord[]; 428 const [firstItem] = selectedRows as DeviceRecord[];
@@ -391,6 +430,11 @@ @@ -391,6 +430,11 @@
391 batchUpdateProductFlag.value = 430 batchUpdateProductFlag.value =
392 !selectedRows.length || 431 !selectedRows.length ||
393 !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType); 432 !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType);
  433 +
  434 + batchPrivateFlag.value = selectedRows.some((item) => !item?.customerId);
  435 + isPublicAndPrivateFlag.value =
  436 + selectedRows.some((item) => !item?.customerId) &&
  437 + selectedRows.some((item) => item?.customerAdditionalInfo?.isPublic);
394 }, 438 },
395 }, 439 },
396 }); 440 });
@@ -492,6 +536,29 @@ @@ -492,6 +536,29 @@
492 openCustomerModal(true, options); 536 openCustomerModal(true, options);
493 }; 537 };
494 538
  539 + const handleBatchOrg = () => {
  540 + const options = getSelectRows();
  541 + openOrgodal(true, options);
  542 + };
  543 +
  544 + // 批量公开设备
  545 + const handleBatchPublic = async () => {
  546 + const options = getSelectRows();
  547 + const tbDeviceIdJoinStr = options.map((item) => item.tbDeviceId).join(',');
  548 + const res = await doBatchPublicDevice(tbDeviceIdJoinStr);
  549 + createMessage.success(res);
  550 + handleReload();
  551 + };
  552 +
  553 + // 批量私有设备
  554 + const handleBatchPrivate = async () => {
  555 + const options = getSelectRows();
  556 + const tbDeviceIdJoinStr = options.map((item) => item.tbDeviceId).join(',');
  557 + const res = await doBatchPrivateDevice(tbDeviceIdJoinStr);
  558 + createMessage.success(res);
  559 + handleReload();
  560 + };
  561 +
495 const handleDelete = async (record?: DeviceRecord) => { 562 const handleDelete = async (record?: DeviceRecord) => {
496 let ids: string[] = []; 563 let ids: string[] = [];
497 if (record) { 564 if (record) {