|
|
@@ -0,0 +1,508 @@
|
|
|
+<template>
|
|
|
+ <a-card title="设备组织树">
|
|
|
+ <a-row :gutter="[8, 8]" >
|
|
|
+ <a-col :lg="5" :md="10" :xs="24">
|
|
|
+ <a-card style="height: 100%;" >
|
|
|
+ <template #title >
|
|
|
+ <a-row justify="space-between" align="middle" >
|
|
|
+ <a-col><a-button type="primary" @click="addDeviceGroup">+ 添加根设备群组</a-button></a-col>
|
|
|
+ <a-col><ReloadIconTsx :loading="state.groupLoading" :@reload="getDeviceGroup"/></a-col>
|
|
|
+ </a-row>
|
|
|
+ </template>
|
|
|
+ <a-spin :spinning="state.groupLoading" >
|
|
|
+ <a-tree
|
|
|
+ v-model:expandedKeys="expandedKeys"
|
|
|
+ :tree-data="state.groupTreeData"
|
|
|
+ defaultExpandAll
|
|
|
+ autoExpandParent
|
|
|
+ :field-names="{
|
|
|
+ key: 'id',
|
|
|
+ title: 'groupLabel',
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <template #title="record">
|
|
|
+ <a-space @click="changeTreeActiveKey(record.key)" >
|
|
|
+ <div class="group-label" > {{ record.groupLabel }} </div>
|
|
|
+ <template v-if="state.treeActiveKey[0] === record.key && record.key" >
|
|
|
+ <plus-circle-outlined @click="openGroupModal('add', record)"/>
|
|
|
+ <delete-outlined @click="openGroupModal('del', record)"/>
|
|
|
+ </template>
|
|
|
+ </a-space>
|
|
|
+ </template>
|
|
|
+ </a-tree>
|
|
|
+ </a-spin>
|
|
|
+ </a-card>
|
|
|
+ </a-col>
|
|
|
+ <a-col :lg="19" :md="14" :xs="24">
|
|
|
+ <a-card style="height: 100%;" >
|
|
|
+ <span v-if="!state.treeActiveKey[0]" >
|
|
|
+ <a-row justify="space-between" >
|
|
|
+ <a-col>
|
|
|
+ <a-space>
|
|
|
+ <a-select
|
|
|
+ allowClear
|
|
|
+ v-model:value="state.queryParams.searchKey"
|
|
|
+ style="width: 170px;"
|
|
|
+ >
|
|
|
+ <a-select-option
|
|
|
+ v-for="item in searchKeys"
|
|
|
+ :key="item.key"
|
|
|
+ :value="item.key"
|
|
|
+ >
|
|
|
+ {{ item.name }}
|
|
|
+ </a-select-option>
|
|
|
+ </a-select>
|
|
|
+ <a-input-search placeholder="查询" allowClear enter-button @search="getDevicePage" ></a-input-search>
|
|
|
+ </a-space>
|
|
|
+ </a-col>
|
|
|
+ <a-col><ReloadIconTsx :loading="state.loading" :reload="getDevicePage"/></a-col>
|
|
|
+ </a-row>
|
|
|
+ <a-table
|
|
|
+ style="margin-top: 20px;"
|
|
|
+ :loading="state.loading"
|
|
|
+ :columns="columns"
|
|
|
+ :data-source="state.groupDateSource"
|
|
|
+ :pagination="state.queryParams"
|
|
|
+ @change="changePage"
|
|
|
+ >
|
|
|
+ <template #bodyCell="{column, record}">
|
|
|
+ <template v-if="column.key === 'status'" >
|
|
|
+ <a-tag>{{record.status}}</a-tag>
|
|
|
+ </template>
|
|
|
+ <template v-if="column.key === 'type'">
|
|
|
+ <a-tag>{{SpaceController.type.find((item)=>{
|
|
|
+ return item.key == record.type
|
|
|
+ })?.label}}</a-tag>
|
|
|
+ </template>
|
|
|
+ <template v-if="column.key === 'deviceCount'">
|
|
|
+ <a @click="pushCvsDevicePage(record)" >{{record.deviceCount}}</a>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </a-table>
|
|
|
+ </span>
|
|
|
+ <span v-else :key="selectGroup?.id">
|
|
|
+ <a-row >
|
|
|
+ <a-col :span="24" >
|
|
|
+ <div style="font-size: 18px;font-weight: 500" >群组名称:{{state.selectGroup?.groupLabel}}</div>
|
|
|
+ </a-col>
|
|
|
+ </a-row>
|
|
|
+ <a-card
|
|
|
+ title="绑定设备"
|
|
|
+ :bordered="false"
|
|
|
+ :headStyle="{border: 'none', padding: '0px'}"
|
|
|
+ >
|
|
|
+ <a-row justify="end" >
|
|
|
+ <a-col>
|
|
|
+ <a-space>
|
|
|
+ <a-button @click="delBind([])" >批量解绑</a-button>
|
|
|
+ <a-button @click="state.drawerVisible = true" >绑定</a-button>
|
|
|
+ <a-button @click="getDeviceByGroup" > 刷新</a-button>
|
|
|
+ </a-space>
|
|
|
+ </a-col>
|
|
|
+ </a-row>
|
|
|
+ <div style="margin-bottom: 10px;" >已选择 {{state.selectedRowKeys.length}} 个设备, 一次最多可批量解绑100个设备。</div>
|
|
|
+ <a-table
|
|
|
+ :rowKey="record => record.deviceId"
|
|
|
+ :columns="groupDeviceColumns"
|
|
|
+ :loading='state.tableLoading'
|
|
|
+ :data-source="state.groupDeviceDataSource"
|
|
|
+ :row-selection="{
|
|
|
+ selectedRowKeys: state.selectedRowKeys,
|
|
|
+ onChange: onSelectChange,
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <template #bodyCell="{column, record}" >
|
|
|
+ <template v-if="column.key === 'action'" >
|
|
|
+ <a-space>
|
|
|
+ <a >详情</a>
|
|
|
+ <a-popconfirm
|
|
|
+ title="确实要取消绑定吗?"
|
|
|
+ ok-text="确定"
|
|
|
+ cancel-text="取消"
|
|
|
+ @confirm="delBind(record.deviceId)"
|
|
|
+ >
|
|
|
+ <a>解绑</a>
|
|
|
+ </a-popconfirm>
|
|
|
+ </a-space>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </a-table>
|
|
|
+ </a-card>
|
|
|
+ </span>
|
|
|
+ </a-card>
|
|
|
+ </a-col>
|
|
|
+ </a-row>
|
|
|
+ <!-- 绑定设备抽屉 -->
|
|
|
+ <a-drawer
|
|
|
+ v-model:open="state.drawerVisible"
|
|
|
+ size="large"
|
|
|
+ class="custom-class"
|
|
|
+ title="绑定设备"
|
|
|
+ placement="right"
|
|
|
+ >
|
|
|
+ <a-alert message="一个设备最多可以被添加到10个设备组中。" type="info" show-icon />
|
|
|
+ <a-row>
|
|
|
+ <a-col>
|
|
|
+ <a-space style="margin-top: 20px;">
|
|
|
+ <a-select allowClear style="width: 100px;" v-model:value="state.queryParams.searchKey">
|
|
|
+ <a-select-option v-for="item in searchKeys" :key="item.key" :value="item.key" >{{item.name}}</a-select-option>
|
|
|
+ </a-select>
|
|
|
+ <a-input allowClear v-model:value="state.queryParams.searchValue" ></a-input>
|
|
|
+ <a-button @click="getDevicePage" > 搜索</a-button>
|
|
|
+ </a-space>
|
|
|
+ </a-col>
|
|
|
+ <a-col>
|
|
|
+ <a-table
|
|
|
+ :rowKey="record => record.deviceId"
|
|
|
+ style="margin-top: 10px;width: 688px;"
|
|
|
+ :loading="state.tableLoading"
|
|
|
+ :columns="groupDeviceColumns"
|
|
|
+ :data-source="state.groupDateSource"
|
|
|
+ :row-selection="{
|
|
|
+ selectedRowKeys: state.selectedRowKeys,
|
|
|
+ onChange: onSelectChange,
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <template #bodyCell="{column, record}">
|
|
|
+ </template>
|
|
|
+ </a-table>
|
|
|
+ </a-col>
|
|
|
+ </a-row>
|
|
|
+
|
|
|
+ <template #footer >
|
|
|
+ <a-row justify="end" >
|
|
|
+ <a-col>
|
|
|
+ <a-space>
|
|
|
+ <a-button type="primary" @click="bindDevice"> 绑定</a-button>
|
|
|
+ <a-button @click="state.drawerVisible = false" > 取消</a-button>
|
|
|
+ </a-space>
|
|
|
+ </a-col>
|
|
|
+ </a-row>
|
|
|
+ </template>
|
|
|
+ </a-drawer>
|
|
|
+
|
|
|
+ <!-- 添加根设备群组 -->
|
|
|
+ <modal-pro
|
|
|
+ :title="state.groupOpera === 'add' ? '添加设备群组' : '删除设备群组'"
|
|
|
+ :open="state.groupModalVisible"
|
|
|
+ @cancel="state.groupModalVisible = false"
|
|
|
+ @ok="ok"
|
|
|
+ >
|
|
|
+
|
|
|
+ <a-form v-if="state.groupOpera === 'add'" :label-col="{span: 6}" :wrapper-col="{span: 16}" >
|
|
|
+ <a-form-item label="父级群组名称" v-if="groupState.upperGroupId" >
|
|
|
+ <a-input allowClear disabled :value="groupState.upperGroupLabel" ></a-input>
|
|
|
+ </a-form-item>
|
|
|
+ <a-form-item label="分组名" v-bind="validateInfos.groupLabel" >
|
|
|
+ <a-input allowClear v-model:value="groupState.groupLabel" ></a-input>
|
|
|
+ </a-form-item>
|
|
|
+ </a-form>
|
|
|
+ <a-row v-else >
|
|
|
+ <a-col>
|
|
|
+ <a-alert style="width: 472px;" v-if="!state.selectTree.hasChildren " message="你确定要删除此群组吗?" type="info" show-icon />
|
|
|
+ <a-alert style="width: 472px;" v-else message="此群组下已创建子群组,不能直接被删除。如要继续,请先审视并删除子群组,再重试删除。" type="error" show-icon />
|
|
|
+ </a-col>
|
|
|
+ </a-row>
|
|
|
+ </modal-pro>
|
|
|
+ </a-card>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script lang="ts" setup >
|
|
|
+import { ReloadIconTsx } from '@/components/MicroComponents'
|
|
|
+import { SpaceController } from '@/controller'
|
|
|
+import { computed, onMounted, reactive, ref } from 'vue'
|
|
|
+import { PlusCircleOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
|
|
+import { Form } from 'ant-design-vue'
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
+import { orgTree } from '@/controller/cvs/orgTree'
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
+
|
|
|
+const useForm = Form.useForm
|
|
|
+
|
|
|
+const columns = [
|
|
|
+ {
|
|
|
+ title: '设备ID',
|
|
|
+ dataIndex: 'deviceId',
|
|
|
+ key: 'deviceId'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '设备名称',
|
|
|
+ dataIndex: 'deviceName',
|
|
|
+ key: 'deviceName'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '空间类型',
|
|
|
+ dataIndex: 'type',
|
|
|
+ key: 'type'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '空间名称',
|
|
|
+ dataIndex: 'spaceName',
|
|
|
+ key: 'spaceName'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '状态',
|
|
|
+ dataIndex: 'status',
|
|
|
+ key: 'status'
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+const groupDeviceColumns = [
|
|
|
+ {
|
|
|
+ title: '设备ID',
|
|
|
+ dataIndex: 'deviceId',
|
|
|
+ key: 'deviceId'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '设备名称',
|
|
|
+ dataIndex: 'deviceName'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '所属空间',
|
|
|
+ dataIndex: 'spaceName'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '操作',
|
|
|
+ dataIndex: 'action',
|
|
|
+ key: 'action'
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+const searchKeys = [
|
|
|
+ { name: '设备ID', key: 'deviceId' },
|
|
|
+ { name: '设备名称', key: 'deviceName' }
|
|
|
+]
|
|
|
+
|
|
|
+interface TreeNode {
|
|
|
+ id: string;
|
|
|
+ createAt: number;
|
|
|
+ updateAt: number | null;
|
|
|
+ deleted: boolean;
|
|
|
+ groupLabel: string;
|
|
|
+ upperGroupId: string;
|
|
|
+ tenantId: string;
|
|
|
+ children?: TreeNode[];
|
|
|
+}
|
|
|
+
|
|
|
+function findParentNodeById (id: number, tree: any[]): any {
|
|
|
+ for (let i = 0; i < tree.length; i++) {
|
|
|
+ const node = tree[i]
|
|
|
+ if (node.id === id) {
|
|
|
+ return node
|
|
|
+ } else if (node.children) {
|
|
|
+ const parentNode = findParentNodeById(id, node.children)
|
|
|
+ if (parentNode) {
|
|
|
+ return parentNode
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null
|
|
|
+}
|
|
|
+
|
|
|
+function buildTree (data: TreeNode[], upperGroupId: string): TreeNode[] {
|
|
|
+ const tree: TreeNode[] = []
|
|
|
+ for (const node of data) {
|
|
|
+ if (node.upperGroupId === upperGroupId) {
|
|
|
+ const children = buildTree(data, node.id)
|
|
|
+ if (children.length) {
|
|
|
+ node.children = children
|
|
|
+ }
|
|
|
+ tree.push({
|
|
|
+ ...node,
|
|
|
+ key: node.id,
|
|
|
+ hasChildren: !!node.children?.length
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return tree
|
|
|
+}
|
|
|
+
|
|
|
+const expandedKeys = ref<string[]>([])
|
|
|
+
|
|
|
+const selectGroup = computed(() => state.groupData.find(item => item.id === state.treeActiveKey[0]))
|
|
|
+
|
|
|
+const state = reactive<{
|
|
|
+ selectTree: Record<string, any>,
|
|
|
+ treeActiveKey: number[]
|
|
|
+ groupData: CVS.Group[],
|
|
|
+ groupTreeData: any,
|
|
|
+ groupLoading: boolean,
|
|
|
+ groupDateSource: CVS.device[],
|
|
|
+ groupDeviceDataSource: CVS.device[]
|
|
|
+ queryParams: any,
|
|
|
+ loading: boolean,
|
|
|
+ tableLoading: boolean
|
|
|
+ drawerVisible: boolean
|
|
|
+ selectedRowKeys: string[],
|
|
|
+ groupModalVisible: boolean,
|
|
|
+ groupOpera: 'add' | 'del',
|
|
|
+ selectGroup: Partial<CVS.Group>
|
|
|
+}>({
|
|
|
+ groupTreeData: [],
|
|
|
+ groupData: [],
|
|
|
+ groupOpera: 'add',
|
|
|
+ groupLoading: false,
|
|
|
+ groupDateSource: [],
|
|
|
+ groupDeviceDataSource: [],
|
|
|
+ treeActiveKey: [0],
|
|
|
+ selectTree: {},
|
|
|
+ loading: false,
|
|
|
+ tableLoading: false,
|
|
|
+ groupModalVisible: false,
|
|
|
+ selectGroup: {},
|
|
|
+ drawerVisible: false,
|
|
|
+ selectedRowKeys: [],
|
|
|
+ queryParams: {
|
|
|
+ page: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ total: 0,
|
|
|
+ spaceId: '',
|
|
|
+ searchKey: 'deviceId',
|
|
|
+ searchValue: ''
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const groupState = reactive({
|
|
|
+ groupLabel: '',
|
|
|
+ upperGroupLabel: '',
|
|
|
+ upperGroupId: ''
|
|
|
+})
|
|
|
+
|
|
|
+const { resetFields, validate, validateInfos } = useForm(groupState, reactive({
|
|
|
+ groupLabel: [{ required: true, message: '请填写分组名称' }]
|
|
|
+}))
|
|
|
+
|
|
|
+const changePage = ({ current }) => {
|
|
|
+ state.queryParams.page = current
|
|
|
+ getDevicePage()
|
|
|
+}
|
|
|
+
|
|
|
+const delBind = async (ids: []) => {
|
|
|
+ const params = (ids.length ? ids : state.selectedRowKeys).map(item => {
|
|
|
+ return {
|
|
|
+ deviceGroupId: state.treeActiveKey[0],
|
|
|
+ deviceId: item
|
|
|
+ }
|
|
|
+ })
|
|
|
+ await orgTree.delGroupBindDevice(params)
|
|
|
+ state.selectedRowKeys = []
|
|
|
+ await getDeviceByGroup()
|
|
|
+}
|
|
|
+
|
|
|
+const bindDevice = async () => {
|
|
|
+ const params = state.selectedRowKeys.map(item => {
|
|
|
+ return {
|
|
|
+ deviceId: item,
|
|
|
+ deviceGroupId: state.treeActiveKey[0]
|
|
|
+ }
|
|
|
+ })
|
|
|
+ await orgTree.postGroupBindDevice(params)
|
|
|
+ state.drawerVisible = false
|
|
|
+ state.selectedRowKeys = []
|
|
|
+ await getDeviceByGroup()
|
|
|
+}
|
|
|
+
|
|
|
+const ok = async () => {
|
|
|
+ if (state.groupOpera === 'add') {
|
|
|
+ validate().then(async () => {
|
|
|
+ await orgTree.postDeviceGroup(groupState)
|
|
|
+ state.groupModalVisible = false
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ if (state.selectTree.hasChildren) {
|
|
|
+ state.groupModalVisible = false
|
|
|
+ } else {
|
|
|
+ console.log('删除设备群组')
|
|
|
+ state.groupModalVisible = false
|
|
|
+ await orgTree.removeDeviceGroup(state.treeActiveKey[0])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ await getDeviceGroup()
|
|
|
+}
|
|
|
+
|
|
|
+const onSelectChange = (rowKeys: string[]) => {
|
|
|
+ state.selectedRowKeys = rowKeys
|
|
|
+}
|
|
|
+
|
|
|
+const openGroupModal = (opra: 'add' | 'del', record: any = {}) => {
|
|
|
+ groupState.upperGroupLabel = ''
|
|
|
+ groupState.upperGroupId = ''
|
|
|
+ groupState.groupLabel = ''
|
|
|
+ state.selectTree = record
|
|
|
+ state.groupModalVisible = true
|
|
|
+ state.groupOpera = opra
|
|
|
+ groupState.upperGroupLabel = record.groupLabel
|
|
|
+ groupState.upperGroupId = record.id
|
|
|
+ console.log(findParentNodeById(record.upperGroupId, state.groupTreeData))
|
|
|
+}
|
|
|
+
|
|
|
+const changeTreeActiveKey = (id: string, e?: TouchEvent) => {
|
|
|
+ e && e.stopPropagation && e.stopPropagation()
|
|
|
+ state.treeActiveKey[0] = id
|
|
|
+ console.log('id1' + id)
|
|
|
+ console.log('id:' + state.treeActiveKey[0])
|
|
|
+ getDeviceByGroup()
|
|
|
+ console.log(state.groupData)
|
|
|
+ // state.selectGroup = state.groupData.find(item => item.id === id)
|
|
|
+}
|
|
|
+
|
|
|
+const addDeviceGroup = (e: TouchEvent) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ state.groupModalVisible = true
|
|
|
+}
|
|
|
+
|
|
|
+const getDeviceByGroup = async () => {
|
|
|
+ state.tableLoading = true
|
|
|
+ const { data } = await orgTree.getDeviceByGroup({
|
|
|
+ page: 1,
|
|
|
+ pageSize: 200,
|
|
|
+ deviceGroupId: typeof state.treeActiveKey === 'object' ? state.treeActiveKey[0] : state.treeActiveKey
|
|
|
+ })
|
|
|
+ state.tableLoading = false
|
|
|
+ state.groupDeviceDataSource = data
|
|
|
+}
|
|
|
+
|
|
|
+const getDevicePage = async () => {
|
|
|
+ state.loading = true
|
|
|
+ const { data, sum } = await SpaceController.deviceGroupPage(state.queryParams)
|
|
|
+ state.loading = false
|
|
|
+ state.groupDateSource = data
|
|
|
+ state.queryParams.total = sum
|
|
|
+}
|
|
|
+
|
|
|
+const getDeviceGroup = async () => {
|
|
|
+ state.groupLoading = true
|
|
|
+ const { data } = await orgTree.listDeviceGroup({ upperGroupId: '' })
|
|
|
+ state.groupTreeData = [
|
|
|
+ {
|
|
|
+ groupLabel: '所有分组',
|
|
|
+ key: '',
|
|
|
+ id: '',
|
|
|
+ children: buildTree(data, '0')
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ console.log('state.groupTreeData:' + state.groupTreeData)
|
|
|
+ state.groupData = data
|
|
|
+ state.groupLoading = false
|
|
|
+ expandedKeys.value = state.groupTreeData[0].children.map(item => item.id)
|
|
|
+}
|
|
|
+
|
|
|
+const pushCvsDevicePage = (record: CVS.space) => {
|
|
|
+ router.push({ path: '/cvs/video/device', query: { spaceId: record.spaceId } })
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ getDeviceGroup()
|
|
|
+ getDevicePage()
|
|
|
+})
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped >
|
|
|
+.group-label {
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+</style>
|