feat: 新增设备管理-子设备

This commit is contained in:
blp 2023-02-06 18:24:19 +08:00
parent a7772d55b5
commit 75e5c67ead
4 changed files with 492 additions and 1 deletions

View File

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

View File

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

View File

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

View File

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