Commit b984eeb26e16616ecf71397e68b0c6d7fe247df0

Authored by 温伟
Committed by xp.Huang
1 parent 08551861

feat:支持设备批量更新产品,优化批量操作按钮

... ... @@ -51,6 +51,11 @@ enum DeviceManagerApi {
51 51 * @description 通过设备列表获取设备信息
52 52 */
53 53 QUERY_DEVICES = '/device/get/devices',
  54 +
  55 + /**
  56 + * @description 批量变更产品
  57 + */
  58 + BATCH_UPDATE_PRODUCT = '/device/batch/update',
54 59 }
55 60
56 61 export const devicePage = (params: DeviceQueryParam) => {
... ... @@ -378,3 +383,13 @@ export const doBatchClearAlarm = (ids: string[]) => {
378 383 { joinPrefix: false }
379 384 );
380 385 };
  386 +
  387 +export const doBatchUpdateProduct = (params: { deviceIds: string[]; deviceProfileId: string }) => {
  388 + return defHttp.post(
  389 + {
  390 + url: DeviceManagerApi.BATCH_UPDATE_PRODUCT,
  391 + data: params,
  392 + },
  393 + { joinPrefix: false }
  394 + );
  395 +};
... ...
... ... @@ -54,7 +54,7 @@
54 54 },
55 55 // api params
56 56 params: {
57   - type: Object as PropType<Recordable>,
  57 + type: [Object, String, Number] as any,
58 58 default: () => ({}),
59 59 },
60 60 // support xxx.xxx.xx
... ...
... ... @@ -44,7 +44,7 @@
44 44 </ModalWrapper>
45 45
46 46 <template #[item]="data" v-for="item in Object.keys(omit($slots, 'default'))">
47   - <slot :name="item" v-bind="data"></slot>
  47 + <slot :name="item" v-bind="data || {}"></slot>
48 48 </template>
49 49 </Modal>
50 50 </template>
... ...
1 1 import { FormProps, FormSchema, useComponentRegister } from '/@/components/Form';
2 2 import { findDictItemByCode } from '/@/api/system/dict';
3   -import { deviceProfile, getGatewayDevice } from '/@/api/device/deviceManager';
  3 +import { getGatewayDevice, queryDeviceProfileBy } from '/@/api/device/deviceManager';
4 4 import { TransportTypeEnum } from '../../profiles/components/TransportDescript/const';
5 5 import { JSONEditorValidator } from '/@/components/CodeEditor/src/JSONEditor';
6 6 import { JSONEditor } from '/@/components/CodeEditor';
... ... @@ -96,15 +96,26 @@ export const step1Schemas: FormSchema[] = [
96 96 show: false,
97 97 },
98 98 {
  99 + field: 'isUpdate',
  100 + label: '编辑模式',
  101 + component: 'Switch',
  102 + show: false,
  103 + },
  104 + {
99 105 field: 'profileId',
100 106 label: '所属产品',
101 107 required: true,
102 108 component: 'ApiSelect',
  109 + helpMessage: [
  110 + '注意:修改设备产品时,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"有关联设备,请自行调整并保存。',
  111 + ],
103 112 componentProps: ({ formActionType, formModel }) => {
104 113 const { setFieldsValue } = formActionType;
105 114 return {
106 115 api: async () => {
107   - const options = await deviceProfile();
  116 + const options = await queryDeviceProfileBy({
  117 + deviceType: formModel?.isUpdate ? formModel?.deviceType : null,
  118 + });
108 119 const { profileId } = formModel;
109 120 if (profileId) {
110 121 const selectRecord = options.find((item) => item.tbProfileId === profileId);
... ...
... ... @@ -7,6 +7,63 @@ import { h } from 'vue';
7 7 import { Tag, Tooltip } from 'ant-design-vue';
8 8 import { handeleCopy } from '../../profiles/step/topic';
9 9
  10 +export enum DeviceListAuthEnum {
  11 + /**
  12 + * @description 新增
  13 + */
  14 + CREATE = 'api:yt:device:post',
  15 +
  16 + /**
  17 + * @description 删除
  18 + */
  19 + DELETE = 'api:yt:device:delete',
  20 +
  21 + /**
  22 + * @description 编辑
  23 + */
  24 + UPDATE = 'api:yt:device:update',
  25 +
  26 + /**
  27 + * @description 详情
  28 + */
  29 + DETAIL = 'api:yt:device:get',
  30 +
  31 + /**
  32 + * @description 导入
  33 + */
  34 + IMPORT = 'api:yt:device:import',
  35 +
  36 + /**
  37 + * @description 公开
  38 + */
  39 + PUBLIC = 'api:yt:device:public',
  40 +
  41 + /**
  42 + * @description 上下线
  43 + */
  44 + ONLINE = 'api:yt:device:online:record',
  45 +
  46 + /**
  47 + * @description 管理设备凭证
  48 + */
  49 + EQUIPMENT = 'api:yt:device:equipment',
  50 +
  51 + /**
  52 + * @description 分配客户
  53 + */
  54 + ASSIGN = 'api:yt:device:assign',
  55 +
  56 + /**
  57 + * @description 命令下发
  58 + */
  59 + RPC = 'api:yt:device:rpc',
  60 +
  61 + /**
  62 + * @description 更新产品
  63 + */
  64 + UPDATE_PRODUCT = 'api:yt:device:update:product',
  65 +}
  66 +
10 67 // 表格列数据
11 68 export const columns: BasicColumn[] = [
12 69 {
... ...
  1 +import { getDeviceProfile } from '/@/api/alarm/position';
  2 +import { FormSchema } from '/@/components/Form';
  3 +
  4 +enum FormFieldsEnum {
  5 + DEVICE_TYPE = 'deviceType',
  6 + SOURCE_DEVICE_PROFILE_ID = 'sourceDeviceProfileName',
  7 + TARGET_DEVICE_PROFILE_ID = 'deviceProfileId',
  8 +}
  9 +
  10 +export type FormGetFiledValueResultType = Record<FormFieldsEnum, string>;
  11 +
  12 +export const formSchemas: FormSchema[] = [
  13 + {
  14 + field: FormFieldsEnum.SOURCE_DEVICE_PROFILE_ID,
  15 + component: 'Input',
  16 + label: '源产品',
  17 + dynamicDisabled: true,
  18 + required: true,
  19 + },
  20 + {
  21 + field: FormFieldsEnum.DEVICE_TYPE,
  22 + label: '设备类型',
  23 + component: 'Input',
  24 + show: false,
  25 + },
  26 + {
  27 + field: FormFieldsEnum.TARGET_DEVICE_PROFILE_ID,
  28 + component: 'ApiSelect',
  29 + label: '目标产品',
  30 + required: true,
  31 + componentProps: ({ formModel }) => {
  32 + return {
  33 + api: getDeviceProfile,
  34 + params: formModel[FormFieldsEnum.DEVICE_TYPE],
  35 + labelField: 'name',
  36 + valueField: 'tbProfileId',
  37 + placeholder: '请选择目标产品',
  38 + getPopupContainer: () => document.body,
  39 + };
  40 + },
  41 + },
  42 +];
... ...
  1 +export { default as BatchUpdateProductModal } from './index.vue';
  2 +
  3 +export type BatchUpdateProductModalParamsType = ModalParamsType<
  4 + Record<'sourceDeviceProfileName' | 'deviceType', string> & Record<'deviceIds', string[]>
  5 +>;
... ...
  1 +<script setup lang="ts">
  2 + import { ref, unref } from 'vue';
  3 + import { BatchUpdateProductModalParamsType } from '.';
  4 + import { FormGetFiledValueResultType, formSchemas } from './config';
  5 + import { doBatchUpdateProduct } from '/@/api/device/deviceManager';
  6 + import { BasicForm, useForm } from '/@/components/Form';
  7 + import Icon from '/@/components/Icon';
  8 + import { BasicModal, useModalInner } from '/@/components/Modal';
  9 + import { useMessage } from '/@/hooks/web/useMessage';
  10 +
  11 + const emits = defineEmits(['register', 'success']);
  12 +
  13 + const deviceIds = ref<string[]>([]);
  14 + const [registerForm, { validate, getFieldsValue, setFieldsValue, resetFields }] = useForm({
  15 + layout: 'vertical',
  16 + showActionButtonGroup: false,
  17 + schemas: formSchemas,
  18 + });
  19 +
  20 + const [registerModal, { setModalProps, closeModal }] = useModalInner(
  21 + async ({ record }: BatchUpdateProductModalParamsType) => {
  22 + resetFields();
  23 + deviceIds.value = record.deviceIds;
  24 + setFieldsValue(record);
  25 + }
  26 + );
  27 +
  28 + const { createMessage } = useMessage();
  29 + const handleOk = async () => {
  30 + try {
  31 + await validate();
  32 + setModalProps({ loading: true, confirmLoading: true });
  33 +
  34 + const result = getFieldsValue() as FormGetFiledValueResultType;
  35 +
  36 + await doBatchUpdateProduct({
  37 + deviceIds: unref(deviceIds),
  38 + deviceProfileId: result.deviceProfileId,
  39 + });
  40 +
  41 + createMessage.success('操作成功');
  42 +
  43 + closeModal();
  44 + emits('success');
  45 + } finally {
  46 + setModalProps({ loading: false, confirmLoading: false });
  47 + }
  48 + };
  49 +</script>
  50 +
  51 +<template>
  52 + <BasicModal @register="registerModal" @ok="handleOk">
  53 + <template #title>
  54 + <div>批量更新产品</div>
  55 + <div class="mt-4 font-normal text-sm">
  56 + <Icon icon="ant-design:info-circle-outlined" />
  57 + <span>
  58 + 注意:修改设备产品时只能修改为同类型产品,如果"大屏"、"组态"、"看板"、"场景联动"、"数据流转"等有关联设备,请自行调整并保存。
  59 + </span>
  60 + </div>
  61 + </template>
  62 + <!-- <template #title>
  63 + <span></span>
  64 + </template> -->
  65 + <BasicForm @register="registerForm" />
  66 + </BasicModal>
  67 +</template>
... ...
... ... @@ -20,7 +20,7 @@
20 20 @next="handleStep1Next"
21 21 ref="DeviceStep1Ref"
22 22 v-show="current === 0"
23   - :isUpdate="!isUpdate"
  23 + :isUpdate="isUpdate"
24 24 />
25 25 <DeviceStep2
26 26 ref="DeviceStep2Ref"
... ... @@ -33,7 +33,7 @@
33 33 </BasicModal>
34 34 </template>
35 35 <script lang="ts">
36   - import { defineComponent, ref, computed, unref } from 'vue';
  36 + import { defineComponent, ref, computed, unref, nextTick } from 'vue';
37 37 import { BasicModal, useModalInner } from '/@/components/Modal';
38 38 import { createOrEditDevice } from '/@/api/device/deviceManager';
39 39 import DeviceStep1 from '../step/DeviceStep1.vue';
... ... @@ -60,16 +60,17 @@
60 60 const DeviceStep2Ref = ref<InstanceType<typeof DeviceStep2>>();
61 61 const { createMessage } = useMessage();
62 62 const current = ref(0);
63   - const isUpdate = ref<Boolean>(false);
  63 + const isUpdate = ref(false);
64 64 const deviceInfo = ref({});
65 65 let currentDeviceData = {} as Recordable;
66 66 const getTitle = computed(() => (!unref(isUpdate) ? '新增设备' : '编辑设备'));
67 67 // 所有参数
68 68 let stepState = ref();
69 69 // 编辑回显
70   - const [register, { closeModal, setModalProps }] = useModalInner((data) => {
  70 + const [register, { closeModal, setModalProps }] = useModalInner(async (data) => {
71 71 setModalProps({ confirmLoading: false, loading: true });
72 72 isUpdate.value = data?.isUpdate;
  73 + await nextTick();
73 74 if (unref(isUpdate)) {
74 75 const { record } = data;
75 76 currentDeviceData = record;
... ...
... ... @@ -36,7 +36,7 @@
36 36 </Input>
37 37 </template>
38 38 </BasicForm>
39   - <div class="flex justify-center" v-if="isUpdate">
  39 + <div class="flex justify-center" v-if="!isUpdate">
40 40 <a-button type="primary" @click="nextStep">下一步</a-button>
41 41 </div>
42 42 </div>
... ... @@ -199,6 +199,7 @@
199 199 showResetButton: false,
200 200 showSubmitButton: false,
201 201 });
  202 +
202 203 async function nextStep() {
203 204 try {
204 205 let values = await validate();
... ... @@ -245,7 +246,7 @@
245 246 const selectPosition = async () => {
246 247 visible.value = true;
247 248 if (
248   - !unref(isUpdate1) &&
  249 + unref(isUpdate1) &&
249 250 unref(devicePositionState).longitude &&
250 251 unref(devicePositionState).latitude
251 252 ) {
... ... @@ -440,8 +441,10 @@
440 441 positionState.address = deviceInfo.address;
441 442 devicePositionState.value = { ...toRaw(positionState) };
442 443 devicePic.value = deviceInfo.avatar;
  444 +
443 445 if (deviceInfo.avatar) {
444 446 setFieldsValue({
  447 + isUpdate: unref(isUpdate1),
445 448 icon: [{ uid: buildUUID(), name: 'name', url: deviceInfo.avatar } as FileItem],
446 449 });
447 450 }
... ... @@ -449,6 +452,7 @@
449 452 ...data,
450 453 code: data?.code,
451 454 addressCode: parseInt(data?.code || '', 16),
  455 + isUpdate: unref(isUpdate1),
452 456 });
453 457 }
454 458 // 父组件调用获取字段值的方法
... ...
... ... @@ -4,41 +4,57 @@
4 4 <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
5 5 <BasicTable style="flex: auto" @register="registerTable" class="w-5/6 xl:w-4/5">
6 6 <template #toolbar>
7   - <Authority value="api:yt:device:post">
  7 + <Authority :value="DeviceListAuthEnum.CREATE">
8 8 <a-button type="primary" @click="handleCreate" v-if="authBtn(role)">
9 9 新增设备
10 10 </a-button>
11 11 </Authority>
12   - <Authority value="api:yt:device:delete">
13   - <Popconfirm
14   - title="您确定要批量删除数据"
15   - ok-text="确定"
16   - cancel-text="取消"
17   - @confirm="handleDelete()"
18   - >
19   - <a-button color="error" v-if="authBtn(role)" :disabled="!isExistOption">
20   - 批量删除
21   - </a-button>
22   - </Popconfirm>
23   - </Authority>
24   - <Authority value="api:yt:device:import">
  12 +
  13 + <Authority :value="DeviceListAuthEnum.IMPORT">
25 14 <Button type="primary" @click="handleBatchImport">导入</Button>
26 15 </Authority>
27   - <Authority value="api:yt:device:assign">
28   - <a-button
  16 +
  17 + <Authority
  18 + :value="[
  19 + DeviceListAuthEnum.DELETE,
  20 + DeviceListAuthEnum.ASSIGN,
  21 + DeviceListAuthEnum.UPDATE_PRODUCT,
  22 + ]"
  23 + >
  24 + <AuthDropDown
29 25 v-if="authBtn(role)"
30   - type="primary"
31   - @click="handleBatchAssign"
32 26 :disabled="!isExistOption"
  27 + :dropMenuList="[
  28 + {
  29 + text: '删除设备',
  30 + auth: DeviceListAuthEnum.DELETE,
  31 + icon: 'ant-design:delete-outlined',
  32 + event: '',
  33 + popconfirm: {
  34 + title: '您确定要批量删除数据',
  35 + onConfirm: () => handleDelete(),
  36 + },
  37 + },
  38 + {
  39 + text: '分配客户',
  40 + auth: DeviceListAuthEnum.ASSIGN,
  41 + icon: 'mdi:account-arrow-left',
  42 + event: '',
  43 + onClick: handleBatchAssign.bind(null),
  44 + },
  45 + {
  46 + text: '更新产品',
  47 + auth: DeviceListAuthEnum.UPDATE_PRODUCT,
  48 + icon: 'clarity:note-edit-line',
  49 + event: '',
  50 + disabled: batchUpdateProductFlag,
  51 + onClick: handelOpenBatchUpdateProductModal,
  52 + },
  53 + ]"
33 54 >
34   - 批量分配
35   - </a-button>
  55 + <Button type="primary" :disabled="!isExistOption">批量操作</Button>
  56 + </AuthDropDown>
36 57 </Authority>
37   - <!-- <Authority>
38   - <a-button type="primary" @click="handelCollect()" :disabled="!isExistOption">
39   - 批量收藏
40   - </a-button>
41   - </Authority> -->
42 58 </template>
43 59 <template #img="{ record }">
44 60 <TableImg
... ... @@ -121,12 +137,12 @@
121 137 {
122 138 label: '详情',
123 139 icon: 'ant-design:eye-outlined',
124   - auth: 'api:yt:device:get',
  140 + auth: DeviceListAuthEnum.DETAIL,
125 141 onClick: handleDetail.bind(null, record),
126 142 },
127 143 {
128 144 label: '编辑',
129   - auth: 'api:yt:device:update',
  145 + auth: DeviceListAuthEnum.UPDATE,
130 146 icon: 'clarity:note-edit-line',
131 147 ifShow: authBtn(role),
132 148 onClick: handleEdit.bind(null, record),
... ... @@ -138,7 +154,7 @@
138 154 label: '取消分配',
139 155 icon: 'mdi:account-arrow-left',
140 156 ifShow: authBtn(role) && !record?.customerAdditionalInfo?.isPublic,
141   - auth: 'api:yt:device:assign',
  157 + auth: DeviceListAuthEnum.ASSIGN,
142 158 popConfirm: {
143 159 title: '是否取消分配客户',
144 160 confirm: handleCancelDispatchCustomer.bind(null, record),
... ... @@ -148,12 +164,12 @@
148 164 label: '分配客户',
149 165 icon: 'mdi:account-arrow-right',
150 166 ifShow: authBtn(role),
151   - auth: 'api:yt:device:assign',
  167 + auth: DeviceListAuthEnum.ASSIGN,
152 168 onClick: handleDispatchCustomer.bind(null, record),
153 169 },
154 170 {
155 171 label: record?.customerAdditionalInfo?.isPublic ? '私有' : '公开',
156   - auth: 'api:yt:device:public',
  172 + auth: DeviceListAuthEnum.PUBLIC,
157 173 icon: record?.customerAdditionalInfo?.isPublic
158 174 ? 'ant-design:lock-outlined'
159 175 : 'ant-design:unlock-outlined',
... ... @@ -161,7 +177,7 @@
161 177 },
162 178 {
163 179 label: '上下线记录',
164   - auth: 'api:yt:device:online:record',
  180 + auth: DeviceListAuthEnum.ONLINE,
165 181 icon: 'ant-design:rise-outlined',
166 182 onClick: handleUpAndDownRecord.bind(null, record),
167 183 },
... ... @@ -173,7 +189,6 @@
173 189 }
174 190 : {
175 191 label: '取消收藏',
176   - auth: 'api:yt:device:online:record',
177 192 icon: 'ant-design:heart-outlined',
178 193 popConfirm: {
179 194 title: '是否取消收藏',
... ... @@ -182,7 +197,7 @@
182 197 },
183 198 {
184 199 label: '删除',
185   - auth: 'api:yt:device:delete',
  200 + auth: DeviceListAuthEnum.DELETE,
186 201 icon: 'ant-design:delete-outlined',
187 202 ifShow: authBtn(role) && record.customerId === undefined,
188 203 color: 'error',
... ... @@ -208,11 +223,16 @@
208 223 <CustomerModal @register="registerCustomerModal" @reload="handleReload" />
209 224
210 225 <BatchImportModal @register="registerImportModal" @import-finally="handleImportFinally" />
  226 +
  227 + <BatchUpdateProductModal
  228 + @register="registerBatchUpdateProductModal"
  229 + @success="handleBatchUpdateProductSuccess"
  230 + />
211 231 </PageWrapper>
212 232 </div>
213 233 </template>
214   -<script lang="ts">
215   - import { defineComponent, reactive, unref, onMounted } from 'vue';
  234 +<script lang="ts" setup>
  235 + import { reactive, onMounted, ref } from 'vue';
216 236 import {
217 237 DeviceModel,
218 238 DeviceRecord,
... ... @@ -220,8 +240,8 @@
220 240 DeviceTypeEnum,
221 241 } from '/@/api/device/model/deviceModel';
222 242 import { BasicTable, useTable, TableAction, TableImg } from '/@/components/Table';
223   - import { columns, searchFormSchema } from './config/device.data';
224   - import { Tag, Popover, Popconfirm, Button, Tooltip } from 'ant-design-vue';
  243 + import { columns, DeviceListAuthEnum, searchFormSchema } from './config/device.data';
  244 + import { Tag, Popover, Button, Tooltip } from 'ant-design-vue';
225 245 import { HeartTwoTone } from '@ant-design/icons-vue';
226 246 import {
227 247 deleteDevice,
... ... @@ -246,326 +266,295 @@
246 266 import { USER_INFO_KEY } from '/@/enums/cacheEnum';
247 267 import { getAuthCache } from '/@/utils/auth';
248 268 import { authBtn } from '/@/enums/roleEnum';
249   - import { useClipboard } from '@vueuse/core';
250 269 import { QuestionCircleOutlined } from '@ant-design/icons-vue';
251 270 import { Authority } from '/@/components/Authority';
252 271 import { useRoute, useRouter } from 'vue-router';
253 272 import { useBatchOperation } from '/@/utils/useBatchOperation';
254 273 import { useSyncConfirm } from '/@/hooks/component/useSyncConfirm';
255 274 import { useAuthDeviceDetail } from './hook/useAuthDeviceDetail';
  275 + import {
  276 + BatchUpdateProductModal,
  277 + BatchUpdateProductModalParamsType,
  278 + } from './cpns/modal/BatchUpdateProductModal';
  279 + import { DataActionModeEnum } from '/@/enums/toolEnum';
  280 + import { AuthDropDown } from '/@/components/Widget';
256 281
257   - export default defineComponent({
258   - name: 'DeviceManagement',
259   - components: {
260   - BasicTable,
261   - PageWrapper,
262   - TableAction,
263   - OrganizationIdTree,
264   - Tag,
265   - DeviceModal,
266   - DeviceDetailDrawer,
267   - CustomerModal,
268   - TableImg,
269   - QuestionCircleOutlined,
270   - Popover,
271   - Authority,
272   - Popconfirm,
273   - BatchImportModal,
274   - Button,
275   - // HeartOutlined,
276   - HeartTwoTone,
277   - Tooltip,
278   - },
279   - setup(_) {
280   - const { isCustomer } = useAuthDeviceDetail();
281   - const { createMessage } = useMessage();
282   - const go = useGo();
283   - const ROUTER = useRouter();
284   - const ROUTE = useRoute();
285   - const searchInfo = reactive<Recordable>({});
286   - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
287   - const [registerModal, { openModal }] = useModal();
288   - const [registerCustomerModal, { openModal: openCustomerModal }] = useModal();
289   - const [registerDetailDrawer, { openDrawer }] = useDrawer();
290   - const [registerTbDetailDrawer, { openDrawer: openTbDeviceDrawer }] = useDrawer();
291   - const [registerGatewayDetailDrawer, { openDrawer: openGatewayDetailDrawer }] = useDrawer();
292   - const [registerImportModal, { openModal: openImportModal }] = useModal();
  282 + const { isCustomer } = useAuthDeviceDetail();
  283 + const { createMessage } = useMessage();
  284 + const go = useGo();
  285 + const ROUTER = useRouter();
  286 + const ROUTE = useRoute();
  287 + const searchInfo = reactive<Recordable>({});
  288 + const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
  289 + const [registerModal, { openModal }] = useModal();
  290 + const [registerCustomerModal, { openModal: openCustomerModal }] = useModal();
  291 + const [registerDetailDrawer, { openDrawer }] = useDrawer();
  292 + const [registerTbDetailDrawer, { openDrawer: openTbDeviceDrawer }] = useDrawer();
  293 + const [registerGatewayDetailDrawer, { openDrawer: openGatewayDetailDrawer }] = useDrawer();
  294 + const [registerImportModal, { openModal: openImportModal }] = useModal();
  295 + const [registerBatchUpdateProductModal, { openModal: openBatchUpdateProductModal }] = useModal();
293 296
294   - const [
295   - registerTable,
296   - {
297   - reload,
298   - setLoading,
299   - setSelectedRowKeys,
300   - getForm,
301   - getSelectRowKeys,
302   - getSelectRows,
303   - getRowSelection,
304   - },
305   - ] = useTable({
306   - title: '设备列表',
307   - api: devicePage,
308   - columns,
309   - beforeFetch: (params) => {
310   - const { deviceProfileId } = params;
311   - if (!deviceProfileId) return;
312   - const obj = {
313   - ...params,
314   - ...{
315   - deviceProfileIds: deviceProfileId ? [deviceProfileId] : null,
316   - },
317   - };
318   - delete obj.deviceProfileId;
319   - return obj;
320   - },
321   - formConfig: {
322   - labelWidth: 100,
323   - schemas: searchFormSchema,
324   - resetFunc: resetFn,
325   - },
326   - useSearchForm: true,
327   - showTableSetting: true,
328   - bordered: true,
329   - showIndexColumn: false,
330   - rowKey: 'id',
331   - searchInfo: searchInfo,
332   - clickToRowSelect: false,
333   - actionColumn: {
334   - width: 200,
335   - title: '操作',
336   - slots: { customRender: 'action' },
337   - fixed: 'right',
338   - },
339   - rowSelection: {
340   - type: 'checkbox',
341   - getCheckboxProps: (record: DeviceModel) => {
342   - return { disabled: !!record.customerId };
343   - },
  297 + const batchUpdateProductFlag = ref(true);
  298 +
  299 + const [
  300 + registerTable,
  301 + {
  302 + reload,
  303 + setLoading,
  304 + setSelectedRowKeys,
  305 + getForm,
  306 + getSelectRowKeys,
  307 + getSelectRows,
  308 + getRowSelection,
  309 + clearSelectedRowKeys,
  310 + },
  311 + ] = useTable({
  312 + title: '设备列表',
  313 + api: devicePage,
  314 + columns,
  315 + beforeFetch: (params) => {
  316 + const { deviceProfileId } = params;
  317 + if (!deviceProfileId) return;
  318 + const obj = {
  319 + ...params,
  320 + ...{
  321 + deviceProfileIds: deviceProfileId ? [deviceProfileId] : null,
344 322 },
345   - });
  323 + };
  324 + delete obj.deviceProfileId;
  325 + return obj;
  326 + },
  327 + formConfig: {
  328 + labelWidth: 100,
  329 + schemas: searchFormSchema,
  330 + resetFunc: resetFn,
  331 + },
  332 + useSearchForm: true,
  333 + showTableSetting: true,
  334 + bordered: true,
  335 + showIndexColumn: false,
  336 + rowKey: 'id',
  337 + searchInfo: searchInfo,
  338 + clickToRowSelect: false,
  339 + actionColumn: {
  340 + width: 200,
  341 + title: '操作',
  342 + slots: { customRender: 'action' },
  343 + fixed: 'right',
  344 + },
  345 + rowSelection: {
  346 + type: 'checkbox',
  347 + getCheckboxProps: (record: DeviceModel) => {
  348 + return { disabled: !!record.customerId };
  349 + },
  350 + onSelect(_record, _selected, selectedRows) {
  351 + const [firstItem] = selectedRows as DeviceRecord[];
  352 + const { deviceType } = firstItem || {};
  353 + batchUpdateProductFlag.value =
  354 + !selectedRows.length ||
  355 + !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType);
  356 + },
  357 + onSelectAll(_selected, selectedRows) {
  358 + const [firstItem] = selectedRows as DeviceRecord[];
  359 + const { deviceType } = firstItem || {};
  360 + batchUpdateProductFlag.value =
  361 + !selectedRows.length ||
  362 + !selectedRows.every((item) => (item as DeviceRecord).deviceType === deviceType);
  363 + },
  364 + },
  365 + });
346 366
347   - const { isExistOption } = useBatchOperation(getRowSelection, setSelectedRowKeys);
  367 + const { isExistOption } = useBatchOperation(getRowSelection, setSelectedRowKeys);
348 368
349   - const userInfo: any = getAuthCache(USER_INFO_KEY);
350   - const role: string = userInfo.roles[0];
  369 + const userInfo: any = getAuthCache(USER_INFO_KEY);
  370 + const role: string = userInfo.roles[0];
351 371
352   - function handleCreate() {
353   - openModal(true, {
354   - isUpdate: false,
355   - });
356   - }
357   - // 分配客户
358   - function handleDispatchCustomer(record: Recordable) {
359   - openCustomerModal(true, record);
360   - }
361   - function handleReload() {
362   - setSelectedRowKeys([]);
363   - handleSuccess();
364   - }
365   - // 取消分配客户
366   - async function handleCancelDispatchCustomer(record: Recordable) {
367   - try {
368   - // 该设备是否正在被场景联动使用中?
369   - await cancelDispatchCustomer(record);
370   - handleReload();
371   - } catch {}
372   - }
  372 + function handleCreate() {
  373 + openModal(true, {
  374 + isUpdate: false,
  375 + });
  376 + }
  377 + // 分配客户
  378 + function handleDispatchCustomer(record: Recordable) {
  379 + openCustomerModal(true, record);
  380 + }
  381 + function handleReload() {
  382 + setSelectedRowKeys([]);
  383 + handleSuccess();
  384 + }
  385 + // 取消分配客户
  386 + async function handleCancelDispatchCustomer(record: Recordable) {
  387 + try {
  388 + // 该设备是否正在被场景联动使用中?
  389 + await cancelDispatchCustomer(record);
  390 + handleReload();
  391 + } catch {}
  392 + }
373 393
374   - function handleDetail(record: Recordable) {
375   - const { id, tbDeviceId, deviceProfile, deviceType } = record;
376   - const { transportType } = deviceProfile || {};
377   - openDrawer(true, {
378   - id,
379   - tbDeviceId,
380   - transportType,
381   - deviceType,
382   - });
383   - }
  394 + function handleDetail(record: Recordable) {
  395 + const { id, tbDeviceId, deviceProfile, deviceType } = record;
  396 + const { transportType } = deviceProfile || {};
  397 + openDrawer(true, {
  398 + id,
  399 + tbDeviceId,
  400 + transportType,
  401 + deviceType,
  402 + });
  403 + }
384 404
385   - async function handleEdit(record: Recordable) {
386   - if (record.deviceType === 'SENSOR') {
387   - const res = await getGATEWAY(record.tbDeviceId);
388   - Reflect.set(record, 'gatewayId', res.tbDeviceId);
389   - }
390   - openModal(true, {
391   - isUpdate: true,
392   - record,
393   - });
394   - }
395   - function handleSuccess() {
396   - reload();
397   - }
398   - function handleSelect(organization) {
399   - searchInfo.organizationId = organization;
400   - handleSuccess();
401   - }
402   - function goDeviceProfile(e) {
403   - go(PageEnum.DEVICE_PROFILE + '?name=' + encodeURIComponent(String(e)));
404   - }
405   - const { copied, copy } = useClipboard({ legacy: true });
406   - const copySN = async (snCode: string) => {
407   - await copy(snCode);
408   - if (unref(copied)) {
409   - createMessage.success('复制成功~');
410   - }
411   - };
  405 + async function handleEdit(record: Recordable) {
  406 + if (record.deviceType === 'SENSOR') {
  407 + const res = await getGATEWAY(record.tbDeviceId);
  408 + Reflect.set(record, 'gatewayId', res.tbDeviceId);
  409 + }
  410 + openModal(true, {
  411 + isUpdate: true,
  412 + record,
  413 + });
  414 + }
  415 + function handleSuccess() {
  416 + reload();
  417 + }
  418 + function handleSelect(organization) {
  419 + searchInfo.organizationId = organization;
  420 + handleSuccess();
  421 + }
  422 + function goDeviceProfile(e) {
  423 + go(PageEnum.DEVICE_PROFILE + '?name=' + encodeURIComponent(String(e)));
  424 + }
412 425
413   - const handleOpenTbDeviceDetail = (data: { id: string; tbDeviceId: string }) => {
414   - openTbDeviceDrawer(true, data);
415   - };
  426 + const handleOpenTbDeviceDetail = (data: { id: string; tbDeviceId: string }) => {
  427 + openTbDeviceDrawer(true, data);
  428 + };
416 429
417   - const handleOpenGatewayDetail = (data: { id: string; tbDeviceId: string }) => {
418   - openGatewayDetailDrawer(true, data);
419   - };
  430 + const handleOpenGatewayDetail = (data: { id: string; tbDeviceId: string }) => {
  431 + openGatewayDetailDrawer(true, data);
  432 + };
420 433
421   - const handleUpAndDownRecord = (record: Record<'name' | 'alias', string>) => {
422   - ROUTER.push({
423   - path: '/operation/onlinerecord',
424   - query: { deviceName: record.alias || record.name },
425   - });
426   - };
  434 + const handleUpAndDownRecord = (record: Record<'name' | 'alias', string>) => {
  435 + ROUTER.push({
  436 + path: '/operation/onlinerecord',
  437 + query: { deviceName: record.alias || record.name },
  438 + });
  439 + };
427 440
428   - const handleCheckHasDiffenterOrg = (options: DeviceModel[]) => {
429   - let orgId: string | undefined;
430   - let flag = false;
431   - for (const item of options) {
432   - const _orgId = item.organizationId;
433   - if (!orgId) orgId = _orgId;
434   - if (orgId !== _orgId) {
435   - flag = true;
436   - break;
437   - }
438   - }
439   - return flag;
440   - };
  441 + const handleCheckHasDiffenterOrg = (options: DeviceModel[]) => {
  442 + let orgId: string | undefined;
  443 + let flag = false;
  444 + for (const item of options) {
  445 + const _orgId = item.organizationId;
  446 + if (!orgId) orgId = _orgId;
  447 + if (orgId !== _orgId) {
  448 + flag = true;
  449 + break;
  450 + }
  451 + }
  452 + return flag;
  453 + };
441 454
442   - const handleBatchAssign = () => {
443   - const options = getSelectRows();
444   - if (handleCheckHasDiffenterOrg(options as DeviceModel[])) {
445   - createMessage.error('当前选中项中存在不同所属组织的设备!');
446   - return;
447   - }
448   - openCustomerModal(true, options);
449   - };
  455 + const handleBatchAssign = () => {
  456 + const options = getSelectRows();
  457 + if (handleCheckHasDiffenterOrg(options as DeviceModel[])) {
  458 + createMessage.error('当前选中项中存在不同所属组织的设备!');
  459 + return;
  460 + }
  461 + openCustomerModal(true, options);
  462 + };
450 463
451   - const handleDelete = async (record?: DeviceRecord) => {
452   - let ids: string[] = [];
453   - if (record) {
454   - ids.push(record.id);
455   - } else {
456   - ids = getSelectRowKeys();
457   - }
458   - try {
459   - setLoading(true);
460   - await deleteDevice(ids);
461   - createMessage.success('删除成功');
462   - handleReload();
463   - } catch (error) {
464   - throw error;
465   - } finally {
466   - setLoading(false);
467   - }
468   - };
  464 + const handleDelete = async (record?: DeviceRecord) => {
  465 + let ids: string[] = [];
  466 + if (record) {
  467 + ids.push(record.id);
  468 + } else {
  469 + ids = getSelectRowKeys();
  470 + }
  471 + try {
  472 + setLoading(true);
  473 + await deleteDevice(ids);
  474 + createMessage.success('删除成功');
  475 + handleReload();
  476 + } catch (error) {
  477 + throw error;
  478 + } finally {
  479 + setLoading(false);
  480 + }
  481 + };
469 482
470   - const handleBatchImport = () => {
471   - openImportModal(true);
472   - };
  483 + const handleBatchImport = () => {
  484 + openImportModal(true);
  485 + };
473 486
474   - const handleImportFinally = () => {
475   - reload();
476   - };
  487 + const handleImportFinally = () => {
  488 + reload();
  489 + };
477 490
478   - const { createSyncConfirm } = useSyncConfirm();
479   - const handlePublicDevice = async (record: DeviceRecord) => {
480   - try {
481   - const publicFlag = record?.customerAdditionalInfo?.isPublic;
482   - const type = publicFlag ? '私有' : '公开';
483   - const flag = await createSyncConfirm({
484   - iconType: 'warning',
485   - title: `您确定要将设备 '${record.name}' 设为${type}吗?`,
486   - content: `确认后,设备及其所有数据将被设为${type}并${
487   - publicFlag ? '不' : ''
488   - }可被其他人访问。`,
489   - });
490   - if (!flag) return;
491   - if (publicFlag) {
492   - await privateDevice(record.tbDeviceId);
493   - } else {
494   - await publicDevice(record.tbDeviceId);
495   - }
496   - reload();
497   - } catch (error) {}
498   - };
  491 + const { createSyncConfirm } = useSyncConfirm();
  492 + const handlePublicDevice = async (record: DeviceRecord) => {
  493 + try {
  494 + const publicFlag = record?.customerAdditionalInfo?.isPublic;
  495 + const type = publicFlag ? '私有' : '公开';
  496 + const flag = await createSyncConfirm({
  497 + iconType: 'warning',
  498 + title: `您确定要将设备 '${record.name}' 设为${type}吗?`,
  499 + content: `确认后,设备及其所有数据将被设为${type}并${
  500 + publicFlag ? '不' : ''
  501 + }可被其他人访问。`,
  502 + });
  503 + if (!flag) return;
  504 + if (publicFlag) {
  505 + await privateDevice(record.tbDeviceId);
  506 + } else {
  507 + await publicDevice(record.tbDeviceId);
  508 + }
  509 + reload();
  510 + } catch (error) {}
  511 + };
499 512
500   - // 收藏 && 批量收藏
501   - const handelCollect = async (record?: Recordable) => {
502   - let ids: string[] = [];
503   - if (record) {
504   - ids.push(record.id);
505   - } else {
506   - ids = await getSelectRowKeys();
507   - }
508   - try {
509   - setLoading(true);
510   - await deviceCollect(ids);
511   - createMessage.success('操作成功');
512   - handleReload();
513   - } catch (error) {
514   - throw error;
515   - } finally {
516   - setLoading(false);
517   - }
518   - };
  513 + // 收藏 && 批量收藏
  514 + const handelCollect = async (record?: Recordable) => {
  515 + let ids: string[] = [];
  516 + if (record) {
  517 + ids.push(record.id);
  518 + } else {
  519 + ids = await getSelectRowKeys();
  520 + }
  521 + try {
  522 + setLoading(true);
  523 + await deviceCollect(ids);
  524 + createMessage.success('操作成功');
  525 + handleReload();
  526 + } catch (error) {
  527 + throw error;
  528 + } finally {
  529 + setLoading(false);
  530 + }
  531 + };
519 532
520   - onMounted(() => {
521   - const queryParams = ROUTE.query as Record<'deviceProfileId', undefined | string>;
522   - const { setFieldsValue } = getForm();
523   - setFieldsValue({ deviceProfileId: queryParams.deviceProfileId });
524   - });
525   - return {
526   - registerTable,
527   - handleCreate,
528   - handleDetail,
529   - handleEdit,
530   - handleSuccess,
531   - goDeviceProfile,
532   - handleSelect,
533   - registerModal,
534   - registerDetailDrawer,
535   - DeviceTypeEnum,
536   - DeviceState,
537   - searchInfo,
538   - organizationIdTreeRef,
539   - handleDispatchCustomer,
540   - handleCancelDispatchCustomer,
541   - registerCustomerModal,
542   - authBtn,
543   - role,
544   - copySN,
545   - isExistOption,
546   - handleDelete,
547   - handelCollect,
548   - // hasBatchDelete,
549   - // handleDeleteOrBatchDelete,
550   - handleReload,
551   - registerTbDetailDrawer,
552   - handleOpenTbDeviceDetail,
553   - handleOpenGatewayDetail,
554   - registerGatewayDetailDrawer,
555   - handleUpAndDownRecord,
556   - handleBatchAssign,
557   - registerImportModal,
558   - handleBatchImport,
559   - handleImportFinally,
560   - handlePublicDevice,
561   - isCustomer,
562   - };
563   - },
  533 + onMounted(() => {
  534 + const queryParams = ROUTE.query as Record<'deviceProfileId', undefined | string>;
  535 + const { setFieldsValue } = getForm();
  536 + setFieldsValue({ deviceProfileId: queryParams.deviceProfileId });
564 537 });
  538 +
  539 + const handelOpenBatchUpdateProductModal = () => {
  540 + const rows: DeviceRecord[] = getSelectRows();
  541 + const [firstItem] = rows;
  542 +
  543 + openBatchUpdateProductModal(true, {
  544 + mode: DataActionModeEnum.UPDATE,
  545 + record: {
  546 + sourceDeviceProfileName: firstItem.deviceProfile.name,
  547 + deviceType: firstItem.deviceType,
  548 + deviceIds: rows.map((item) => item.tbDeviceId),
  549 + },
  550 + } as BatchUpdateProductModalParamsType);
  551 + };
  552 +
  553 + const handleBatchUpdateProductSuccess = () => {
  554 + reload();
  555 + clearSelectedRowKeys();
  556 + batchUpdateProductFlag.value = true;
  557 + };
565 558 </script>
566 559
567   -<style scoped lang="css">
568   - /* /deep/.ant-message-notice-content {
569   - max-width: 600px !important;
570   - } */
571   -</style>
  560 +<style scoped lang="css"></style>
... ...