| ... | ... | @@ -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> | 
... | ... |  |