9 changed files with 543 additions and 1 deletions
@ -0,0 +1,64 @@ |
|||||||
|
import type { DeviceGroup, GetdeviceListByGroupParams } from './types' |
||||||
|
import { defHttp } from '@/utils/http/axios' |
||||||
|
|
||||||
|
export function getDeviceGroupTree() { |
||||||
|
return defHttp.get({ |
||||||
|
url: '/deviceGroup/tree', |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export function getDevicegroupDetail(id: string) { |
||||||
|
return defHttp.get({ |
||||||
|
url: '/deviceGroup/detail', |
||||||
|
params: { |
||||||
|
id, |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export function getDeviceListByGroup(params: GetdeviceListByGroupParams) { |
||||||
|
return defHttp.get({ |
||||||
|
url: '/device/pageByGroup', |
||||||
|
params, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export function createDeviceGroup(data: Partial<DeviceGroup>) { |
||||||
|
return defHttp.post({ |
||||||
|
url: '/deviceGroup/save', |
||||||
|
data, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export function updateDeviceGroup(data: Partial<DeviceGroup>) { |
||||||
|
return defHttp.post({ |
||||||
|
url: '/deviceGroup/update', |
||||||
|
data, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export function deleteDevicegroup(id: string) { |
||||||
|
return defHttp.post({ |
||||||
|
url: `/deviceGroup/remove?id=${id}`, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export function bindingDeviceToGroup(deviceGroupId: string, deviceIds: string) { |
||||||
|
return defHttp.post({ |
||||||
|
url: '/deviceGroup/bindDevice', |
||||||
|
data: { |
||||||
|
deviceGroupId, |
||||||
|
deviceIds, |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export function unbindingDeviceFromGroup(deviceGroupId: string, deviceIds: string) { |
||||||
|
return defHttp.post({ |
||||||
|
url: '/deviceGroup/unbindDevice', |
||||||
|
data: { |
||||||
|
deviceGroupId, |
||||||
|
deviceIds, |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
export interface DeviceGroup { |
||||||
|
id: string |
||||||
|
parentId: string |
||||||
|
groupName: string |
||||||
|
remark: string |
||||||
|
} |
||||||
|
|
||||||
|
export interface GetdeviceListByGroupParams extends PageParam { |
||||||
|
groupId?: string |
||||||
|
productId?: string |
||||||
|
deviceSn?: string |
||||||
|
} |
@ -0,0 +1,101 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { BasicDrawer, useDrawerInner } from '@/components/Drawer' |
||||||
|
import { BasicTable, useTable } from '@/components/Table' |
||||||
|
import { getDeviceList } from '@/api/device-manage/device' |
||||||
|
import { useMessage } from '@/hooks/web/useMessage' |
||||||
|
import { bindingDeviceToGroup } from '@/api/device-manage/group' |
||||||
|
import { useSharedProducts } from '@/views/subscription/list/data' |
||||||
|
import { noop } from '@/utils' |
||||||
|
|
||||||
|
const emit = defineEmits(['register', 'success']) |
||||||
|
|
||||||
|
let deviceGroupId: string |
||||||
|
const [register, { closeDrawer, setDrawerProps }] = useDrawerInner((id: string) => { |
||||||
|
deviceGroupId = id |
||||||
|
}) |
||||||
|
|
||||||
|
const { products } = useSharedProducts() |
||||||
|
|
||||||
|
const [registerTable, { getSelectRowKeys, clearSelectedRowKeys }] = useTable({ |
||||||
|
api: getDeviceList, |
||||||
|
rowKey: 'id', |
||||||
|
columns: [ |
||||||
|
{ |
||||||
|
title: '所属产品', |
||||||
|
dataIndex: 'productId', |
||||||
|
customRender({ value }) { |
||||||
|
return products.value.find(item => item.id === value)?.productName |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '产品名称', |
||||||
|
dataIndex: 'deviceName', |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '设备序列号', |
||||||
|
dataIndex: 'deviceSn', |
||||||
|
}, |
||||||
|
], |
||||||
|
formConfig: { |
||||||
|
schemas: [ |
||||||
|
{ |
||||||
|
field: 'productId', |
||||||
|
label: '所属产品', |
||||||
|
component: 'Select', |
||||||
|
componentProps: { |
||||||
|
options: products as any, |
||||||
|
showSearch: true, |
||||||
|
fieldNames: { |
||||||
|
label: 'productName', |
||||||
|
value: 'id', |
||||||
|
}, |
||||||
|
}, |
||||||
|
colProps: { span: 8 }, |
||||||
|
}, |
||||||
|
{ |
||||||
|
field: 'deviceSn', |
||||||
|
label: '设备序列号', |
||||||
|
component: 'Input', |
||||||
|
colProps: { span: 8 }, |
||||||
|
}, |
||||||
|
], |
||||||
|
labelWidth: 90, |
||||||
|
}, |
||||||
|
bordered: true, |
||||||
|
canResize: false, |
||||||
|
useSearchForm: true, |
||||||
|
inset: true, |
||||||
|
rowSelection: {}, |
||||||
|
}) |
||||||
|
|
||||||
|
function handleSubmit() { |
||||||
|
const keys = getSelectRowKeys() |
||||||
|
if (!keys.length) |
||||||
|
return useMessage().createMessage.warn('请先选择设备') |
||||||
|
|
||||||
|
setDrawerProps({ confirmLoading: true }) |
||||||
|
bindingDeviceToGroup(deviceGroupId, keys.join(',')) |
||||||
|
.then(() => { |
||||||
|
useMessage().createMessage.success('绑定成功') |
||||||
|
clearSelectedRowKeys() |
||||||
|
closeDrawer() |
||||||
|
emit('success') |
||||||
|
}) |
||||||
|
.catch(noop) |
||||||
|
.finally(() => { |
||||||
|
setDrawerProps({ confirmLoading: false }) |
||||||
|
}) |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<BasicDrawer |
||||||
|
title="绑定设备" |
||||||
|
width="900px" |
||||||
|
show-footer |
||||||
|
@register="register" |
||||||
|
@ok="handleSubmit" |
||||||
|
> |
||||||
|
<BasicTable @register="registerTable" /> |
||||||
|
</BasicDrawer> |
||||||
|
</template> |
@ -0,0 +1,86 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { ref } from 'vue' |
||||||
|
import { BasicModal, useModalInner } from '@/components/Modal' |
||||||
|
import { BasicForm, useForm } from '@/components/Form' |
||||||
|
import { createDeviceGroup, getDevicegroupDetail, updateDeviceGroup } from '@/api/device-manage/group' |
||||||
|
import type { DeviceGroup } from '@/api/device-manage/group/types' |
||||||
|
import { useMessage } from '@/hooks/web/useMessage' |
||||||
|
import { noop } from '@/utils' |
||||||
|
|
||||||
|
const emit = defineEmits(['register', 'success']) |
||||||
|
|
||||||
|
const [registerForm, { validate, setFieldsValue }] = useForm({ |
||||||
|
schemas: [ |
||||||
|
{ |
||||||
|
field: 'groupName', |
||||||
|
fields: ['parentId', 'id'], |
||||||
|
label: '群组名称', |
||||||
|
required: true, |
||||||
|
component: 'Input', |
||||||
|
componentProps: { |
||||||
|
maxlength: 30, |
||||||
|
showCount: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
field: 'remark', |
||||||
|
label: '群组描述', |
||||||
|
component: 'InputTextArea', |
||||||
|
componentProps: { |
||||||
|
rows: 5, |
||||||
|
}, |
||||||
|
}, |
||||||
|
], |
||||||
|
labelWidth: 80, |
||||||
|
baseColProps: { span: 24 }, |
||||||
|
showActionButtonGroup: false, |
||||||
|
actionColOptions: { span: 23 }, |
||||||
|
}) |
||||||
|
|
||||||
|
const modalTitle = ref('新增根群组') |
||||||
|
|
||||||
|
const [register, { setModalProps, closeModal }] = useModalInner((data: { parentId: string } | { id: string }) => { |
||||||
|
if ('id' in data) { |
||||||
|
modalTitle.value = '编辑群组' |
||||||
|
setModalProps({ loading: true, confirmLoading: true }) |
||||||
|
getDevicegroupDetail(data.id) |
||||||
|
.then((res) => { |
||||||
|
setFieldsValue(res) |
||||||
|
}) |
||||||
|
.catch(noop) |
||||||
|
.finally(() => { |
||||||
|
setModalProps({ loading: false, confirmLoading: false }) |
||||||
|
}) |
||||||
|
} |
||||||
|
else { |
||||||
|
modalTitle.value = '新增子群组' |
||||||
|
setFieldsValue(data) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
async function handleSubmit() { |
||||||
|
try { |
||||||
|
const values = await validate<DeviceGroup>() |
||||||
|
setModalProps({ confirmLoading: true }) |
||||||
|
await (values.id ? updateDeviceGroup(values) : createDeviceGroup(values)) |
||||||
|
closeModal() |
||||||
|
emit('success') |
||||||
|
useMessage().createMessage.success('保存成功') |
||||||
|
} |
||||||
|
catch {} |
||||||
|
finally { |
||||||
|
setModalProps({ confirmLoading: false }) |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<BasicModal |
||||||
|
:title="modalTitle" |
||||||
|
:after-close="() => modalTitle = '新增根群组'" |
||||||
|
@register="register" |
||||||
|
@ok="handleSubmit" |
||||||
|
> |
||||||
|
<BasicForm @register="registerForm" /> |
||||||
|
</BasicModal> |
||||||
|
</template> |
@ -0,0 +1,96 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { ref, watch } from 'vue' |
||||||
|
import { PlusOutlined, SyncOutlined } from '@ant-design/icons-vue' |
||||||
|
import { useAsyncState } from '@vueuse/core' |
||||||
|
import { Popconfirm, Space, Tree } from 'ant-design-vue' |
||||||
|
import GroupFormModal from './GroupFormModal.vue' |
||||||
|
import { useModal } from '@/components/Modal' |
||||||
|
import { deleteDevicegroup, getDeviceGroupTree } from '@/api/device-manage/group' |
||||||
|
import { useMessage } from '@/hooks/web/useMessage' |
||||||
|
|
||||||
|
defineProps<{ selectedGroupId: string | undefined }>() |
||||||
|
const emit = defineEmits(['update:selectedGroupId']) |
||||||
|
|
||||||
|
const [registerModal, { openModal }] = useModal<{ id: string } | { parentId: string }>() |
||||||
|
|
||||||
|
const { state, execute } = useAsyncState(getDeviceGroupTree, [], { resetOnExecute: false }) |
||||||
|
|
||||||
|
async function handleDelete(id: string) { |
||||||
|
try { |
||||||
|
await deleteDevicegroup(id) |
||||||
|
useMessage().createMessage.success('删除成功') |
||||||
|
execute() |
||||||
|
} |
||||||
|
catch {} |
||||||
|
} |
||||||
|
|
||||||
|
const selectedKeys = ref<string[]>([]) |
||||||
|
watch(selectedKeys, (keys, oldKeys) => { |
||||||
|
const value = keys[0] |
||||||
|
if (!value) { |
||||||
|
// can't unselect |
||||||
|
selectedKeys.value = oldKeys |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
emit('update:selectedGroupId', value) |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div rounded="6px" px="10px" pt="12px" pb="6px" border-box bg-white min-w="360px"> |
||||||
|
<div> |
||||||
|
<div flex="~ items-center justify-between" h="35px"> |
||||||
|
<a-button size="small" @click="openModal()"> |
||||||
|
<PlusOutlined /> |
||||||
|
添加根分组 |
||||||
|
</a-button> |
||||||
|
|
||||||
|
<a-button size="small" @click="execute"> |
||||||
|
<SyncOutlined /> |
||||||
|
刷新 |
||||||
|
</a-button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div mt="20px"> |
||||||
|
<Tree v-model:selected-keys="selectedKeys" :tree-data="state" :field-names="{ key: 'id' }"> |
||||||
|
<template #title="{ title, data }"> |
||||||
|
<div flex="~ items-center justify-between" py="8px" px="5px" box-border> |
||||||
|
<div flex="1" width="0" truncate :title="title"> |
||||||
|
{{ title }} |
||||||
|
</div> |
||||||
|
|
||||||
|
<Space> |
||||||
|
<span class="i-ant-design:plus-outlined" title="添加子分组" @click="openModal(true, { parentId: data.id })" /> |
||||||
|
<span class="i-ant-design:edit-outlined" title="编辑分组" @click="openModal(true, { id: data.id })" /> |
||||||
|
<Popconfirm |
||||||
|
title="是否要删除数据?" |
||||||
|
:class="[data.hasChildren ? 'text-gray-300 cursor-not-allowed' : '']" |
||||||
|
:disabled="data.hasChildren" |
||||||
|
@confirm="handleDelete(data.id)" |
||||||
|
> |
||||||
|
<span class="i-ant-design:delete-outlined" :title="data.hasChildren ? '存在子分组, 无法删除' : '删除分组'" /> |
||||||
|
</Popconfirm> |
||||||
|
</Space> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</Tree> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<GroupFormModal @register="registerModal" @success="execute" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="less"> |
||||||
|
:deep(.ant-tree-treenode) { |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
:deep(.ant-tree-node-content-wrapper) { |
||||||
|
flex: 1; |
||||||
|
width: 0; |
||||||
|
} |
||||||
|
:deep(.ant-tree-switcher-icon) { |
||||||
|
margin-top: 15px; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,2 @@ |
|||||||
|
export { default as GroupList } from './GroupList.vue' |
||||||
|
export { default as BindingDeviceDrawer } from './BindingDeviceDrawer.vue' |
@ -0,0 +1,60 @@ |
|||||||
|
import { h } from 'vue' |
||||||
|
import { Tag } from 'ant-design-vue' |
||||||
|
import type { BasicColumn, FormSchema } from '@/components/Table' |
||||||
|
import { useSharedProducts } from '@/views/subscription/list/data' |
||||||
|
|
||||||
|
export function useColumns(): BasicColumn[] { |
||||||
|
const { products } = useSharedProducts() |
||||||
|
return [ |
||||||
|
{ |
||||||
|
title: '所属产品', |
||||||
|
dataIndex: 'productId', |
||||||
|
customRender({ value }) { |
||||||
|
return products.value.find(item => item.id === value)?.productName |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '设备序列号', |
||||||
|
dataIndex: 'deviceSn', |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: '是否在线', |
||||||
|
dataIndex: 'isOnline', |
||||||
|
customRender({ value }) { |
||||||
|
return h(Tag, { |
||||||
|
color: value ? 'green' : 'default', |
||||||
|
}, () => value ? '在线' : '离线') |
||||||
|
}, |
||||||
|
}, |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
export function useSearchFormSchemas(): FormSchema[] { |
||||||
|
const { products } = useSharedProducts() |
||||||
|
return [ |
||||||
|
{ |
||||||
|
label: '所属产品', |
||||||
|
field: 'productId', |
||||||
|
component: 'Select', |
||||||
|
componentProps: { |
||||||
|
options: products as any, |
||||||
|
showSearch: true, |
||||||
|
fieldNames: { |
||||||
|
label: 'productName', |
||||||
|
value: 'id', |
||||||
|
}, |
||||||
|
}, |
||||||
|
colProps: { |
||||||
|
span: 8, |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: '设备序列号', |
||||||
|
field: 'deviceSn', |
||||||
|
component: 'Input', |
||||||
|
colProps: { |
||||||
|
span: 8, |
||||||
|
}, |
||||||
|
}, |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,121 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { ref, watch } from 'vue' |
||||||
|
import { Space } from 'ant-design-vue' |
||||||
|
import { DisconnectOutlined, LinkOutlined } from '@ant-design/icons-vue' |
||||||
|
import { BindingDeviceDrawer, GroupList } from './components' |
||||||
|
import { useColumns, useSearchFormSchemas } from './data' |
||||||
|
import { BasicTable, TableAction, useTable } from '@/components/Table' |
||||||
|
import { useMessage } from '@/hooks/web/useMessage' |
||||||
|
import { getDeviceListByGroup, unbindingDeviceFromGroup } from '@/api/device-manage/group' |
||||||
|
import { useDrawer } from '@/components/Drawer' |
||||||
|
import { noop } from '@/utils' |
||||||
|
|
||||||
|
const [register, { getSelectRowKeys, reload }] = useTable({ |
||||||
|
async api(params) { |
||||||
|
if (!selectedGroupId.value) |
||||||
|
return [] |
||||||
|
|
||||||
|
return getDeviceListByGroup({ |
||||||
|
...params, |
||||||
|
groupId: selectedGroupId.value, |
||||||
|
}) |
||||||
|
}, |
||||||
|
rowKey: 'id', |
||||||
|
columns: useColumns(), |
||||||
|
formConfig: { |
||||||
|
schemas: useSearchFormSchemas(), |
||||||
|
labelWidth: 90, |
||||||
|
}, |
||||||
|
rowSelection: {}, |
||||||
|
bordered: true, |
||||||
|
canResize: false, |
||||||
|
useSearchForm: true, |
||||||
|
actionColumn: { |
||||||
|
width: 210, |
||||||
|
title: '操作', |
||||||
|
dataIndex: 'action', |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
const [registerBindingDeviceDrawer, { openDrawer }] = useDrawer() |
||||||
|
|
||||||
|
const selectedGroupId = ref<string | undefined>() |
||||||
|
watch(selectedGroupId, reload) |
||||||
|
|
||||||
|
const message = useMessage() |
||||||
|
function handleBindingDivice() { |
||||||
|
if (!selectedGroupId.value) |
||||||
|
return message.createMessage.warn('请先选择分组') |
||||||
|
|
||||||
|
openDrawer(true, selectedGroupId.value) |
||||||
|
} |
||||||
|
function handleUnbindingDivice(id?: string) { |
||||||
|
const selectionKeys = id ? [id] : getSelectRowKeys() |
||||||
|
if (!selectionKeys.length) |
||||||
|
return message.createMessage.warn('请先选择设备') |
||||||
|
|
||||||
|
function execute() { |
||||||
|
unbindingDeviceFromGroup(selectedGroupId.value!, selectionKeys.join(',')) |
||||||
|
.then(() => { |
||||||
|
message.createMessage.success('解绑成功') |
||||||
|
reload() |
||||||
|
}) |
||||||
|
.catch(noop) |
||||||
|
} |
||||||
|
|
||||||
|
if (id) |
||||||
|
return execute() |
||||||
|
|
||||||
|
// 批量绑定设备二次确认 |
||||||
|
message.createConfirm({ |
||||||
|
iconType: 'warning', |
||||||
|
title: '是否要解绑选择的设备?', |
||||||
|
onOk: execute, |
||||||
|
}) |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div flex="~"> |
||||||
|
<GroupList v-model:selectedGroupId="selectedGroupId" my="12px" ml="12px" /> |
||||||
|
<BasicTable flex="1" @register="register"> |
||||||
|
<template #tableTitle> |
||||||
|
<Space> |
||||||
|
<a-button @click="handleBindingDivice"> |
||||||
|
<LinkOutlined /> |
||||||
|
批量绑定设备 |
||||||
|
</a-button> |
||||||
|
<a-button @click="handleUnbindingDivice()"> |
||||||
|
<DisconnectOutlined /> |
||||||
|
批量解绑设备 |
||||||
|
</a-button> |
||||||
|
</Space> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template #bodyCell="{ column, record }"> |
||||||
|
<template v-if="column.key === 'action'"> |
||||||
|
<TableAction |
||||||
|
:actions="[ |
||||||
|
{ |
||||||
|
icon: 'i-ant-design:file-search-outlined', |
||||||
|
label: '设备详情', |
||||||
|
onClick: () => $router.push(`/device-manage/device/detail/${record.id}`), |
||||||
|
}, |
||||||
|
{ |
||||||
|
icon: 'i-ant-design:edit-outlined', |
||||||
|
label: '解绑设备', |
||||||
|
popConfirm: { |
||||||
|
title: '是否要解绑该设备?', |
||||||
|
placement: 'left', |
||||||
|
confirm: () => handleUnbindingDivice(record.id), |
||||||
|
}, |
||||||
|
}, |
||||||
|
]" |
||||||
|
/> |
||||||
|
</template> |
||||||
|
</template> |
||||||
|
</BasicTable> |
||||||
|
|
||||||
|
<BindingDeviceDrawer @register="registerBindingDeviceDrawer" @success="reload" /> |
||||||
|
</div> |
||||||
|
</template> |
Loading…
Reference in new issue