feat: 新增设备管理-子设备
This commit is contained in:
		
							parent
							
								
									a7772d55b5
								
							
						
					
					
						commit
						75e5c67ead
					
				|  | @ -217,3 +217,28 @@ export const queryMetric = (deviceId: string, propertyId: string) => server.get( | |||
|  * @returns  | ||||
|  */ | ||||
| export const saveMetric = (deviceId: string, propertyId: string, data: Record<string, any>) => server.patch(`/device-instance/${deviceId}/metric/property/${propertyId}`, data) | ||||
| 
 | ||||
| /** | ||||
|  * 解绑子设备 | ||||
|  * @param deviceId 设备id | ||||
|  * @param childrenId 子设备id | ||||
|  * @param data  | ||||
|  * @returns  | ||||
|  */ | ||||
| export const unbindDevice = (deviceId: string, childrenId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/unbind/${childrenId}`, data) | ||||
| 
 | ||||
| /** | ||||
|  * 批量解绑子设备 | ||||
|  * @param deviceId 设备id | ||||
|  * @param data  | ||||
|  * @returns  | ||||
|  */ | ||||
| export const unbindBatchDevice = (deviceId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/unbind`, data) | ||||
| 
 | ||||
| /** | ||||
|  * 子设备绑定 | ||||
|  * @param deviceId 设备id | ||||
|  * @param data  | ||||
|  * @returns  | ||||
|  */ | ||||
| export const bindDevice = (deviceId: string, data: Record<string, any>) => server.post(`/device/gateway/${deviceId}/bind`, data) | ||||
|  |  | |||
|  | @ -0,0 +1,195 @@ | |||
| <!-- 绑定设备 --> | ||||
| <template> | ||||
|     <a-modal | ||||
|         :maskClosable="false" | ||||
|         width="1000px" | ||||
|         :visible="true" | ||||
|         title="绑定子设备" | ||||
|         okText="确定" | ||||
|         cancelText="取消" | ||||
|         @ok="handleOk" | ||||
|         @cancel="handleCancel" | ||||
|         :confirmLoading="btnLoading" | ||||
|     > | ||||
|         <div style="margin-top: 10px"> | ||||
|             <Search | ||||
|                 :columns="columns" | ||||
|                 target="child-device-bind" | ||||
|                 @search="handleSearch" | ||||
|                 type="simple" | ||||
|             /> | ||||
|             <JTable | ||||
|                 ref="bindDeviceRef" | ||||
|                 :columns="columns" | ||||
|                 :request="query" | ||||
|                 model="TABLE" | ||||
|                 :defaultParams="{ | ||||
|                     terms: [ | ||||
|                         { | ||||
|                             terms: [ | ||||
|                                 { column: 'parentId$isnull', value: '1' }, | ||||
|                                 { | ||||
|                                     column: 'parentId$not', | ||||
|                                     value: detail.id, | ||||
|                                     type: 'or', | ||||
|                                 }, | ||||
|                             ], | ||||
|                         }, | ||||
|                         { | ||||
|                             terms: [ | ||||
|                                 { | ||||
|                                     column: 'id$not', | ||||
|                                     value: detail.id, | ||||
|                                     type: 'and', | ||||
|                                 }, | ||||
|                             ], | ||||
|                         }, | ||||
|                         { | ||||
|                             terms: [ | ||||
|                                 { | ||||
|                                     termType: 'eq', | ||||
|                                     column: 'deviceType', | ||||
|                                     value: 'childrenDevice', | ||||
|                                 }, | ||||
|                             ], | ||||
|                             type: 'and', | ||||
|                         }, | ||||
|                     ], | ||||
|                 }" | ||||
|                 :rowSelection="{ | ||||
|                     selectedRowKeys: _selectedRowKeys, | ||||
|                     onChange: onSelectChange, | ||||
|                 }" | ||||
|                 @cancelSelect="cancelSelect" | ||||
|                 :params="params" | ||||
|             > | ||||
|                 <template #registryTime="slotProps"> | ||||
|                     {{ | ||||
|                         slotProps.registryTime | ||||
|                             ? moment(slotProps.registryTime).format( | ||||
|                                   'YYYY-MM-DD HH:mm:ss', | ||||
|                               ) | ||||
|                             : '' | ||||
|                     }} | ||||
|                 </template> | ||||
|                 <template #state="slotProps"> | ||||
|                     <a-badge | ||||
|                         :text="slotProps.state.text" | ||||
|                         :status="statusMap.get(slotProps.state.value)" | ||||
|                     /> | ||||
|                 </template> | ||||
|             </JTable> | ||||
|         </div> | ||||
|     </a-modal> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| import { query, bindDevice } from '@/api/device/instance'; | ||||
| import moment from 'moment'; | ||||
| import { message } from 'ant-design-vue'; | ||||
| import { useInstanceStore } from '@/store/instance'; | ||||
| import { storeToRefs } from 'pinia'; | ||||
| 
 | ||||
| const instanceStore = useInstanceStore(); | ||||
| const { detail } = storeToRefs(instanceStore); | ||||
| 
 | ||||
| const emit = defineEmits(['change']); | ||||
| 
 | ||||
| const bindDeviceRef = ref<Record<string, any>>({}); | ||||
| const params = ref<Record<string, any>>({}); | ||||
| const _selectedRowKeys = ref<string[]>([]); | ||||
| const btnLoading = ref<boolean>(false); | ||||
| 
 | ||||
| const statusMap = new Map(); | ||||
| statusMap.set('online', 'success'); | ||||
| statusMap.set('offline', 'error'); | ||||
| statusMap.set('notActive', 'warning'); | ||||
| 
 | ||||
| const columns = [ | ||||
|     { | ||||
|         title: 'ID', | ||||
|         dataIndex: 'id', | ||||
|         key: 'id', | ||||
|         ellipsis: true, | ||||
|         fixed: 'left', | ||||
|         search: { | ||||
|             type: 'string', | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         title: '设备名称', | ||||
|         dataIndex: 'name', | ||||
|         key: 'name', | ||||
|         ellipsis: true, | ||||
|         search: { | ||||
|             type: 'string', | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         title: '所属产品', | ||||
|         dataIndex: 'productName', | ||||
|         key: 'productName', | ||||
|         search: { | ||||
|             type: 'string', | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         title: '注册时间', | ||||
|         dataIndex: 'registryTime', | ||||
|         key: 'registryTime', | ||||
|         scopedSlots: true, | ||||
|         search: { | ||||
|             type: 'date', | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         title: '状态', | ||||
|         dataIndex: 'state', | ||||
|         key: 'state', | ||||
|         scopedSlots: true, | ||||
|         search: { | ||||
|             type: 'select', | ||||
|             options: [ | ||||
|                 { label: '禁用', value: 'notActive' }, | ||||
|                 { label: '离线', value: 'offline' }, | ||||
|                 { label: '在线', value: 'online' }, | ||||
|             ], | ||||
|         }, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| const handleSearch = (e: any) => { | ||||
|     params.value = e; | ||||
| }; | ||||
| 
 | ||||
| const onSelectChange = (keys: string[], rows: string[]) => { | ||||
|     _selectedRowKeys.value = [...keys]; | ||||
| }; | ||||
| 
 | ||||
| const cancelSelect = () => { | ||||
|     _selectedRowKeys.value = []; | ||||
| }; | ||||
| 
 | ||||
| const handleOk = () => { | ||||
|     if (_selectedRowKeys.value.length === 0) { | ||||
|         message.warning('请选择需要绑定的设备'); | ||||
|         return; | ||||
|     } | ||||
|     btnLoading.value = true; | ||||
|     bindDevice(detail.value.id, _selectedRowKeys.value) | ||||
|         .then((resp) => { | ||||
|             emit('change', true); | ||||
|             cancelSelect(); | ||||
|             message.success('操作成功'); | ||||
|         }) | ||||
|         .finally(() => { | ||||
|             btnLoading.value = false; | ||||
|         }); | ||||
| }; | ||||
| 
 | ||||
| const handleCancel = () => { | ||||
|     emit('change', false); | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="less"></style> | ||||
|  | @ -0,0 +1,265 @@ | |||
| <template> | ||||
|     <a-card> | ||||
|         <Search | ||||
|             :columns="columns" | ||||
|             target="child-device" | ||||
|             @search="handleSearch" | ||||
|             class="child-device-search" | ||||
|         /> | ||||
|         <JTable | ||||
|             ref="childDeviceRef" | ||||
|             :columns="columns" | ||||
|             :request="query" | ||||
|             :defaultParams="{ | ||||
|                 terms: [ | ||||
|                     { | ||||
|                         column: 'parentId', | ||||
|                         value: detail?.id || '', | ||||
|                         termType: 'eq', | ||||
|                     }, | ||||
|                 ], | ||||
|             }" | ||||
|             :rowSelection="{ | ||||
|                 selectedRowKeys: _selectedRowKeys, | ||||
|                 onChange: onSelectChange, | ||||
|             }" | ||||
|             @cancelSelect="cancelSelect" | ||||
|             :params="params" | ||||
|             :model="'TABLE'" | ||||
|         > | ||||
|             <template #headerTitle> | ||||
|                 <a-space> | ||||
|                     <a-button type="primary"> 新增并绑定 </a-button> | ||||
|                     <a-button type="primary" @click="visible = true"> | ||||
|                         绑定 | ||||
|                     </a-button> | ||||
|                     <a-popconfirm title="确认解绑吗?" @confirm="handleUnBind"> | ||||
|                         <a-button type="primary"> 批量解绑 </a-button> | ||||
|                     </a-popconfirm> | ||||
|                 </a-space> | ||||
|             </template> | ||||
|             <template #registryTime="slotProps"> | ||||
|                 {{ | ||||
|                     slotProps.registryTime | ||||
|                         ? moment(slotProps.registryTime).format( | ||||
|                               'YYYY-MM-DD HH:mm:ss', | ||||
|                           ) | ||||
|                         : '' | ||||
|                 }} | ||||
|             </template> | ||||
|             <template #state="slotProps"> | ||||
|                 <a-badge | ||||
|                     :text="slotProps.state.text" | ||||
|                     :status="statusMap.get(slotProps.state.value)" | ||||
|                 /> | ||||
|             </template> | ||||
|             <template #action="slotProps"> | ||||
|                 <a-space :size="16"> | ||||
|                     <a-tooltip | ||||
|                         v-for="i in getActions(slotProps)" | ||||
|                         :key="i.key" | ||||
|                         v-bind="i.tooltip" | ||||
|                     > | ||||
|                         <a-popconfirm v-if="i.popConfirm" v-bind="i.popConfirm"> | ||||
|                             <a-button | ||||
|                                 :disabled="i.disabled" | ||||
|                                 style="padding: 0" | ||||
|                                 type="link" | ||||
|                                 ><AIcon :type="i.icon" | ||||
|                             /></a-button> | ||||
|                         </a-popconfirm> | ||||
|                         <a-button | ||||
|                             style="padding: 0" | ||||
|                             type="link" | ||||
|                             v-else | ||||
|                             @click="i.onClick && i.onClick(slotProps)" | ||||
|                         > | ||||
|                             <a-button | ||||
|                                 :disabled="i.disabled" | ||||
|                                 style="padding: 0" | ||||
|                                 type="link" | ||||
|                                 ><AIcon :type="i.icon" | ||||
|                             /></a-button> | ||||
|                         </a-button> | ||||
|                     </a-tooltip> | ||||
|                 </a-space> | ||||
|             </template> | ||||
|         </JTable> | ||||
|         <BindChildDevice v-if="visible" @change="closeBindDevice" /> | ||||
|     </a-card> | ||||
| </template> | ||||
| 
 | ||||
| <script setup lang="ts"> | ||||
| import moment from 'moment'; | ||||
| import type { ActionsType } from '@/components/Table'; | ||||
| import { query, unbindDevice, unbindBatchDevice } from '@/api/device/instance'; | ||||
| import { useInstanceStore } from '@/store/instance'; | ||||
| import { storeToRefs } from 'pinia'; | ||||
| import { message } from 'ant-design-vue'; | ||||
| import BindChildDevice from './BindChildDevice/index.vue'; | ||||
| 
 | ||||
| const instanceStore = useInstanceStore(); | ||||
| const { detail } = storeToRefs(instanceStore); | ||||
| const router = useRouter(); | ||||
| 
 | ||||
| const statusMap = new Map(); | ||||
| statusMap.set('online', 'success'); | ||||
| statusMap.set('offline', 'error'); | ||||
| statusMap.set('notActive', 'warning'); | ||||
| 
 | ||||
| const childDeviceRef = ref<Record<string, any>>({}); | ||||
| const params = ref<Record<string, any>>({}); | ||||
| const _selectedRowKeys = ref<string[]>([]); | ||||
| const visible = ref<boolean>(false); | ||||
| 
 | ||||
| const columns = [ | ||||
|     { | ||||
|         title: 'ID', | ||||
|         dataIndex: 'id', | ||||
|         key: 'id', | ||||
|         ellipsis: true, | ||||
|     }, | ||||
|     { | ||||
|         title: '设备名称', | ||||
|         dataIndex: 'name', | ||||
|         key: 'name', | ||||
|         ellipsis: true, | ||||
|         search: { | ||||
|             type: 'string', | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         title: '所属产品', | ||||
|         dataIndex: 'productName', | ||||
|         key: 'productName', | ||||
|         search: { | ||||
|             type: 'string', | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         title: '注册时间', | ||||
|         dataIndex: 'registryTime', | ||||
|         key: 'registryTime', | ||||
|         scopedSlots: true, | ||||
|         search: { | ||||
|             type: 'date', | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         title: '状态', | ||||
|         dataIndex: 'state', | ||||
|         key: 'state', | ||||
|         scopedSlots: true, | ||||
|         search: { | ||||
|             type: 'select', | ||||
|             options: [ | ||||
|                 { label: '禁用', value: 'notActive' }, | ||||
|                 { label: '离线', value: 'offline' }, | ||||
|                 { label: '在线', value: 'online' }, | ||||
|             ], | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         title: '说明', | ||||
|         dataIndex: 'describe', | ||||
|         key: 'describe', | ||||
|         ellipsis: true, | ||||
|         search: { | ||||
|             type: 'string', | ||||
|         }, | ||||
|     }, | ||||
|     { | ||||
|         title: '操作', | ||||
|         key: 'action', | ||||
|         fixed: 'right', | ||||
|         width: 200, | ||||
|         scopedSlots: true, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| const getActions = (data: Partial<Record<string, any>>): ActionsType[] => { | ||||
|     if (!data) return []; | ||||
|     return [ | ||||
|         { | ||||
|             key: 'view', | ||||
|             text: '查看', | ||||
|             tooltip: { | ||||
|                 title: '查看', | ||||
|             }, | ||||
|             icon: 'EyeOutlined', | ||||
|             onClick: () => { | ||||
|                 router.push('/iot/device/instance/detail/' + data.id); | ||||
|             }, | ||||
|         }, | ||||
|         { | ||||
|             key: 'unbind', | ||||
|             text: '解绑', | ||||
|             tooltip: { | ||||
|                 title: '解绑', | ||||
|             }, | ||||
|             icon: 'DisconnectOutlined', | ||||
|             popConfirm: { | ||||
|                 title: '确认解绑吗?', | ||||
|                 okText: '确定', | ||||
|                 cancelText: '取消', | ||||
|                 onConfirm: async () => { | ||||
|                     const resp = await unbindDevice( | ||||
|                         detail.value.id, | ||||
|                         data.id, | ||||
|                         {}, | ||||
|                     ); | ||||
|                     if (resp.status === 200) { | ||||
|                         childDeviceRef.value?.reload(); | ||||
|                         message.success('操作成功!'); | ||||
|                     } | ||||
|                 }, | ||||
|             }, | ||||
|         }, | ||||
|     ]; | ||||
| }; | ||||
| 
 | ||||
| const handleSearch = (e: any) => { | ||||
|     params.value = e; | ||||
| }; | ||||
| 
 | ||||
| const onSelectChange = (keys: string[]) => { | ||||
|     _selectedRowKeys.value = [...keys]; | ||||
| }; | ||||
| 
 | ||||
| const cancelSelect = () => { | ||||
|     _selectedRowKeys.value = []; | ||||
| }; | ||||
| 
 | ||||
| const handleUnBind = async () => { | ||||
|     if (_selectedRowKeys.value.length) { | ||||
|         const resp = await unbindBatchDevice( | ||||
|             detail.value.id, | ||||
|             _selectedRowKeys.value, | ||||
|         ); | ||||
|         if (resp.status === 200) { | ||||
|             message.success('操作成功!'); | ||||
|             cancelSelect(); | ||||
|             childDeviceRef.value?.reload(); | ||||
|         } | ||||
|     } else { | ||||
|         message.warning('请勾选需要解绑的数据'); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| const closeBindDevice = (val: boolean) => { | ||||
|     visible.value = false; | ||||
|     if (val) { | ||||
|         childDeviceRef.value?.reload(); | ||||
|     } | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="less"> | ||||
| .child-device-search { | ||||
|     border-bottom: 1px solid #f0f0f0; | ||||
| } | ||||
| 
 | ||||
| :deep(._jtable-body_1eyxz_1 ._jtable-body-header_1eyxz_6) { | ||||
|     justify-content: flex-end; | ||||
| } | ||||
| </style> | ||||
|  | @ -43,6 +43,7 @@ import { useInstanceStore } from '@/store/instance'; | |||
| import Info from './Info/index.vue'; | ||||
| import Running from './Running/index.vue' | ||||
| import Metadata from '../../components/Metadata/index.vue'; | ||||
| import ChildDevice from './ChildDevice/index.vue'; | ||||
| import { _deploy, _disconnect } from '@/api/device/instance' | ||||
| import { message } from 'ant-design-vue'; | ||||
| import { getImage } from '@/utils/comm'; | ||||
|  | @ -67,13 +68,18 @@ const list = [ | |||
|     { | ||||
|         key: 'Metadata', | ||||
|         tab: '物模型' | ||||
|     }, | ||||
|     { | ||||
|         key: 'ChildDevice', | ||||
|         tab: '子设备' | ||||
|     } | ||||
| ] | ||||
| 
 | ||||
| const tabs = { | ||||
|   Info, | ||||
|   Metadata, | ||||
|   Running | ||||
|   Running, | ||||
|   ChildDevice, | ||||
| } | ||||
| 
 | ||||
| watch( | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue