Browse Source

feat: 设备管理 - 设备分组

main
刘凯 1 year ago
parent
commit
7cdd9ffd5e
  1. 2
      src/App.vue
  2. 64
      src/api/device-manage/group/index.ts
  3. 12
      src/api/device-manage/group/types.ts
  4. 101
      src/views/device-manage/group/components/BindingDeviceDrawer.vue
  5. 86
      src/views/device-manage/group/components/GroupFormModal.vue
  6. 96
      src/views/device-manage/group/components/GroupList.vue
  7. 2
      src/views/device-manage/group/components/index.ts
  8. 60
      src/views/device-manage/group/data.ts
  9. 121
      src/views/device-manage/group/index.vue

2
src/App.vue

@ -19,7 +19,7 @@ const componentSize = computed(() => appStore.getComponentSize)
useTitle()
function transformCellText(props: TransformCellTextProps) {
if (!props.text || !props.text.length)
if (!props.text || (Array.isArray(props.text) && !props.text.length))
return '-'
return props.text

64
src/api/device-manage/group/index.ts

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

12
src/api/device-manage/group/types.ts

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

101
src/views/device-manage/group/components/BindingDeviceDrawer.vue

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

86
src/views/device-manage/group/components/GroupFormModal.vue

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

96
src/views/device-manage/group/components/GroupList.vue

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

2
src/views/device-manage/group/components/index.ts

@ -0,0 +1,2 @@
export { default as GroupList } from './GroupList.vue'
export { default as BindingDeviceDrawer } from './BindingDeviceDrawer.vue'

60
src/views/device-manage/group/data.ts

@ -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,
},
},
]
}

121
src/views/device-manage/group/index.vue

@ -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…
Cancel
Save