|
|
@@ -1,453 +0,0 @@
|
|
|
-<template>
|
|
|
- <div class="menu-wrapper">
|
|
|
- <div class="menu-container" :class="{ collapsed: isCollapse }">
|
|
|
- <header class="menu-header" :class="{ collapsed: isCollapse }" />
|
|
|
- <div class="custom-menu" :class="{ collapsed: isCollapse }">
|
|
|
- <div class="add-group">
|
|
|
- <AddItem content="新建分组" style="cursor: pointer" />
|
|
|
- </div>
|
|
|
- <ul class="menu-list">
|
|
|
- <!-- 父菜单项 -->
|
|
|
- <template v-for="menu in cameraList" :key="menu.id">
|
|
|
- <!-- 父级菜单 -->
|
|
|
- <li
|
|
|
- class="menu-item parent"
|
|
|
- draggable="true"
|
|
|
- @dragstart="dragStart($event, menu.id, null)"
|
|
|
- @dragover.prevent
|
|
|
- @drop="onDrop($event, menu.id, null)"
|
|
|
- >
|
|
|
- <div class="menu-content">
|
|
|
- <AddItem :content="menu.name" :icon="ParentNotActiveIcon">
|
|
|
- <template #prefix>
|
|
|
- <el-icon @click.stop="toggleSubmenu(menu.id)"
|
|
|
- ><component :is="openSubmenu === menu.id ? ArrowDown : ArrowRight"
|
|
|
- /></el-icon>
|
|
|
- </template>
|
|
|
- <template #suffix>
|
|
|
- <el-popover
|
|
|
- trigger="click"
|
|
|
- placement="right-start"
|
|
|
- :show-arrow="false"
|
|
|
- :hide-after="0"
|
|
|
- popper-class="parent-operation-popover"
|
|
|
- >
|
|
|
- <template #reference>
|
|
|
- <img :src="OperationIcon" class="parent-operation-icon" />
|
|
|
- </template>
|
|
|
- <div class="menu-operations">
|
|
|
- <span>停止播放</span>
|
|
|
- <span>删除分组</span>
|
|
|
- </div>
|
|
|
- </el-popover>
|
|
|
- </template>
|
|
|
- </AddItem>
|
|
|
- </div>
|
|
|
- </li>
|
|
|
-
|
|
|
- <!-- 子菜单项(平铺) -->
|
|
|
- <div class="submenu-wrapper" :class="{ expanded: openSubmenu === menu.id }">
|
|
|
- <li class="menu-item">
|
|
|
- <div class="menu-content">
|
|
|
- <AddItem content="添加相机" style="cursor: pointer" />
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="menu-content"
|
|
|
- v-for="submenu in menu.children"
|
|
|
- :key="`${menu.id}-${submenu.id}`"
|
|
|
- :class="{
|
|
|
- active: activeMenu === `${menu.id}-${submenu.id}`,
|
|
|
- dragging: isDragging && draggedItem === `${menu.id}-${submenu.id}`,
|
|
|
- }"
|
|
|
- @click.stop="setActiveMenu(`${menu.id}-${submenu.id}`)"
|
|
|
- draggable="true"
|
|
|
- @dragstart="dragStart($event, menu.id, submenu.id)"
|
|
|
- @dragend="dragEnd"
|
|
|
- @dragover.prevent
|
|
|
- @drop="onDrop($event, menu.id, submenu.id)"
|
|
|
- >
|
|
|
- <AddItem
|
|
|
- v-if="menu.children"
|
|
|
- :content="submenu.name"
|
|
|
- :active="activeMenu === `${menu.id}-${submenu.id}`"
|
|
|
- :icon="activeMenu === `${menu.id}-${submenu.id}` ? ChildActiveIcon : ChildNotActiveIcon"
|
|
|
- >
|
|
|
- <template #suffix>
|
|
|
- <el-icon class="operation-icon"><Delete /></el-icon>
|
|
|
- </template>
|
|
|
- </AddItem>
|
|
|
- </div>
|
|
|
- </li>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </ul>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- 将按钮放在外部容器中 -->
|
|
|
- <div class="toggle-button-wrapper" @click="toggleCollapse">
|
|
|
- <div class="collapse-button" :class="{ collapsed: isCollapse }" />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script lang="ts" setup>
|
|
|
- import { onMounted, ref } from 'vue';
|
|
|
- import { getCameraList } from '@/api/monitor';
|
|
|
- import type { CameraListResponse } from '@/types/monitor';
|
|
|
- import AddItem from '@/components/AddItem.vue';
|
|
|
- import ChildActiveIcon from '../svg/camera-active.svg';
|
|
|
- import ParentNotActiveIcon from '../svg/folder-not-active.svg';
|
|
|
- import ChildNotActiveIcon from '../svg/camera-not-active.svg';
|
|
|
- import OperationIcon from '../svg/operate.svg';
|
|
|
- import { ArrowRight, ArrowDown, Delete } from '@element-plus/icons-vue';
|
|
|
- const cameraList = ref<CameraListResponse[]>([]);
|
|
|
- //获取摄像头列表
|
|
|
- const getCameraListData = async () => {
|
|
|
- const res = await getCameraList();
|
|
|
- cameraList.value = res;
|
|
|
- };
|
|
|
-
|
|
|
- const isCollapse = ref(false);
|
|
|
- const activeMenu = ref<string | number>('');
|
|
|
- // 改为单个字符串,实现手风琴效果
|
|
|
- const openSubmenu = ref<string | number>('');
|
|
|
- // 拖拽状态
|
|
|
- const isDragging = ref(false);
|
|
|
- const draggedItem = ref<string | null>(null);
|
|
|
-
|
|
|
- const toggleCollapse = () => {
|
|
|
- isCollapse.value = !isCollapse.value;
|
|
|
- };
|
|
|
-
|
|
|
- const toggleSubmenu = (menuId: string | number) => {
|
|
|
- // 手风琴效果:如果点击当前已打开的菜单,则关闭;否则打开点击的菜单
|
|
|
- if (openSubmenu.value === menuId) {
|
|
|
- openSubmenu.value = '';
|
|
|
- } else {
|
|
|
- openSubmenu.value = menuId;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const setActiveMenu = (menuId: string | number) => {
|
|
|
- activeMenu.value = menuId;
|
|
|
- };
|
|
|
-
|
|
|
- // 拖拽开始
|
|
|
- const dragStart = (event: DragEvent, parentId: string | number, childId: string | number | null) => {
|
|
|
- isDragging.value = true;
|
|
|
- const itemId = childId ? `${parentId}-${childId}` : `${parentId}`;
|
|
|
- draggedItem.value = itemId;
|
|
|
- if (event.dataTransfer) {
|
|
|
- event.dataTransfer.effectAllowed = 'move';
|
|
|
- event.dataTransfer.setData('text/plain', itemId);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // 拖拽结束
|
|
|
- const dragEnd = () => {
|
|
|
- isDragging.value = false;
|
|
|
- draggedItem.value = null;
|
|
|
- };
|
|
|
-
|
|
|
- // 放置处理
|
|
|
- const onDrop = (event: DragEvent, targetParentId: string | number, targetChildId: string | number | null) => {
|
|
|
- event.preventDefault();
|
|
|
-
|
|
|
- if (!event.dataTransfer) return;
|
|
|
-
|
|
|
- const draggedItemId = event.dataTransfer.getData('text/plain');
|
|
|
- const targetItemId = targetChildId ? `${targetParentId}-${targetChildId}` : `${targetParentId}`;
|
|
|
-
|
|
|
- if (draggedItemId === targetItemId) return;
|
|
|
-
|
|
|
- // 解析拖拽项的父子ID
|
|
|
- const [draggedParentId, draggedChildId] = draggedItemId.includes('-')
|
|
|
- ? draggedItemId.split('-')
|
|
|
- : [draggedItemId, null];
|
|
|
-
|
|
|
- // 这里实现重新排序逻辑
|
|
|
- reorderItems(draggedParentId, draggedChildId, targetParentId, targetChildId);
|
|
|
-
|
|
|
- isDragging.value = false;
|
|
|
- draggedItem.value = null;
|
|
|
- };
|
|
|
-
|
|
|
- // 重新排序项目
|
|
|
- const reorderItems = (
|
|
|
- draggedParentId: string | number,
|
|
|
- draggedChildId: string | number | null,
|
|
|
- targetParentId: string | number,
|
|
|
- targetChildId: string | number | null,
|
|
|
- ) => {
|
|
|
- // 如果是组间拖拽
|
|
|
- if (draggedParentId !== targetParentId) {
|
|
|
- // 找到源组和目标组
|
|
|
- const sourceParentIndex = cameraList.value.findIndex((item) => item.id === draggedParentId);
|
|
|
- const targetParentIndex = cameraList.value.findIndex((item) => item.id === targetParentId);
|
|
|
-
|
|
|
- if (sourceParentIndex === -1 || targetParentIndex === -1) return;
|
|
|
-
|
|
|
- // 如果是拖拽子项到另一个组
|
|
|
- if (draggedChildId !== null) {
|
|
|
- const sourceParent = cameraList.value[sourceParentIndex];
|
|
|
- if (!sourceParent.children) return;
|
|
|
-
|
|
|
- // 找到要移动的子项
|
|
|
- const childIndex = sourceParent.children.findIndex((item) => item.id === draggedChildId);
|
|
|
- if (childIndex === -1) return;
|
|
|
-
|
|
|
- const movedChild = sourceParent.children[childIndex];
|
|
|
-
|
|
|
- // 从源组中移除
|
|
|
- sourceParent.children.splice(childIndex, 1);
|
|
|
-
|
|
|
- // 添加到目标组
|
|
|
- const targetParent = cameraList.value[targetParentIndex];
|
|
|
- if (!targetParent.children) {
|
|
|
- targetParent.children = [];
|
|
|
- }
|
|
|
-
|
|
|
- // 如果有目标子项,则插入到目标子项之前
|
|
|
- if (targetChildId !== null) {
|
|
|
- const targetChildIndex = targetParent.children.findIndex((item) => item.id === targetChildId);
|
|
|
- if (targetChildIndex !== -1) {
|
|
|
- targetParent.children.splice(targetChildIndex, 0, movedChild);
|
|
|
- } else {
|
|
|
- targetParent.children.push(movedChild);
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 否则添加到目标组的末尾
|
|
|
- targetParent.children.push(movedChild);
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 组之间的拖拽,重新排序组
|
|
|
- const movedGroup = { ...cameraList.value[sourceParentIndex] };
|
|
|
- cameraList.value.splice(sourceParentIndex, 1);
|
|
|
-
|
|
|
- // 找到目标位置并插入
|
|
|
- const newTargetIndex = cameraList.value.findIndex((item) => item.id === targetParentId);
|
|
|
- cameraList.value.splice(newTargetIndex, 0, movedGroup);
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 同一组内的拖拽
|
|
|
- const parentIndex = cameraList.value.findIndex((item) => item.id === draggedParentId);
|
|
|
- if (parentIndex === -1) return;
|
|
|
-
|
|
|
- const parent = cameraList.value[parentIndex];
|
|
|
-
|
|
|
- // 如果是子项之间的拖拽
|
|
|
- if (draggedChildId !== null && targetChildId !== null && parent.children) {
|
|
|
- const draggedIndex = parent.children.findIndex((item) => item.id === draggedChildId);
|
|
|
- const targetIndex = parent.children.findIndex((item) => item.id === targetChildId);
|
|
|
-
|
|
|
- if (draggedIndex === -1 || targetIndex === -1) return;
|
|
|
-
|
|
|
- // 移动项
|
|
|
- const movedItem = parent.children[draggedIndex];
|
|
|
- parent.children.splice(draggedIndex, 1);
|
|
|
- parent.children.splice(targetIndex, 0, movedItem);
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- onMounted(() => {
|
|
|
- getCameraListData();
|
|
|
- });
|
|
|
-</script>
|
|
|
-
|
|
|
-<style lang="scss" scoped>
|
|
|
- $menu-width: 240cpx;
|
|
|
- $transition-duration: 0.5s;
|
|
|
- .menu-wrapper {
|
|
|
- position: relative;
|
|
|
- width: $menu-width;
|
|
|
- height: 100%;
|
|
|
- }
|
|
|
-
|
|
|
- .menu-container {
|
|
|
- position: relative;
|
|
|
- width: $menu-width;
|
|
|
- height: 100%;
|
|
|
- }
|
|
|
-
|
|
|
- .menu-header {
|
|
|
- width: $menu-width;
|
|
|
- height: 20cpx;
|
|
|
- background-color: $primary-color;
|
|
|
- border-radius: 8cpx 8cpx 0 0;
|
|
|
- transition: width $transition-duration ease-in-out;
|
|
|
- &.collapsed {
|
|
|
- width: 0;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .custom-menu {
|
|
|
- width: $menu-width;
|
|
|
- height: calc(100% - 5cpx);
|
|
|
- padding-top: 20cpx;
|
|
|
- background-color: #f4f7ff;
|
|
|
- border: none;
|
|
|
- border-radius: 4cpx;
|
|
|
- position: absolute;
|
|
|
- top: 5cpx;
|
|
|
- z-index: 1;
|
|
|
- transition: width $transition-duration ease-in-out;
|
|
|
- overflow: hidden;
|
|
|
-
|
|
|
- &.collapsed {
|
|
|
- width: 0;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .add-group {
|
|
|
- width: $menu-width;
|
|
|
- padding: 12cpx 20cpx;
|
|
|
- &:hover {
|
|
|
- background-color: rgba($primary-color, 0.2);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .menu-list {
|
|
|
- list-style: none;
|
|
|
- width: $menu-width;
|
|
|
- }
|
|
|
-
|
|
|
- .submenu-wrapper {
|
|
|
- max-height: 0;
|
|
|
- overflow: hidden;
|
|
|
- transition: max-height 0.3s ease-in-out;
|
|
|
-
|
|
|
- &.expanded {
|
|
|
- max-height: 300cpx;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .menu-item {
|
|
|
- transition: background-color 0.3s;
|
|
|
- width: 100%;
|
|
|
-
|
|
|
- /* 父菜单和没有子节点的菜单项hover效果 */
|
|
|
- &:hover {
|
|
|
- background-color: rgba($primary-color, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- .menu-content {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- padding: 12cpx 20cpx;
|
|
|
- width: $menu-width;
|
|
|
- box-sizing: border-box;
|
|
|
-
|
|
|
- &.dragging {
|
|
|
- opacity: 0.5;
|
|
|
- border: 1px dashed $primary-color;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- &.submenu-item {
|
|
|
- cursor: pointer;
|
|
|
- &.active .menu-content {
|
|
|
- background-color: $primary-color;
|
|
|
- color: $white-color;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /* 单独设置子菜单中每个menu-content的hover效果 */
|
|
|
- .submenu-wrapper .menu-content {
|
|
|
- padding-left: 50cpx;
|
|
|
- &:hover {
|
|
|
- background-color: rgba($primary-color, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- &.active {
|
|
|
- background-color: $primary-color;
|
|
|
- }
|
|
|
-
|
|
|
- &.dragging {
|
|
|
- opacity: 0.5;
|
|
|
- border: 1px dashed $primary-color;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /* 移除子菜单项的容器hover效果,保留内部元素hover效果 */
|
|
|
- .submenu-wrapper .menu-item:hover {
|
|
|
- background-color: transparent;
|
|
|
- }
|
|
|
-
|
|
|
- .toggle-button-wrapper {
|
|
|
- @include flex-center;
|
|
|
- position: absolute;
|
|
|
- left: 240cpx;
|
|
|
- top: 50%;
|
|
|
- transform: translateY(-50%);
|
|
|
- width: 15cpx;
|
|
|
- height: 75cpx;
|
|
|
- z-index: 10;
|
|
|
- transition: left 0.5s ease-in-out;
|
|
|
- cursor: pointer;
|
|
|
- clip-path: polygon(0 0, 100% 10cpx, 100% 65cpx, 0 75cpx);
|
|
|
- background-color: $primary-color;
|
|
|
-
|
|
|
- .menu-container.collapsed + & {
|
|
|
- left: 0;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .collapse-button {
|
|
|
- width: 0;
|
|
|
- height: 0;
|
|
|
- border-style: solid;
|
|
|
- border-width: 5cpx 0 5cpx 8cpx;
|
|
|
- border-color: transparent transparent transparent $white-color;
|
|
|
- transform: rotate(180deg);
|
|
|
- transition: transform 0.5s ease-in-out;
|
|
|
-
|
|
|
- &.collapsed {
|
|
|
- transform: rotate(0deg);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /* 子菜单操作图标样式 */
|
|
|
- .operation-icon {
|
|
|
- font-size: 16cpx;
|
|
|
- color: $text-color;
|
|
|
- cursor: pointer;
|
|
|
- opacity: 0;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- color: $primary-color;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /* 鼠标悬停时显示子菜单操作按钮 */
|
|
|
- .menu-content:hover .operation-icon {
|
|
|
- opacity: 1;
|
|
|
- }
|
|
|
-
|
|
|
- /* 当菜单项处于激活状态时,操作图标颜色应为白色 */
|
|
|
- .menu-content.active .operation-icon {
|
|
|
- color: $white-color;
|
|
|
- opacity: 1;
|
|
|
- }
|
|
|
- .menu-operations {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- text-align: center;
|
|
|
- gap: 10cpx;
|
|
|
- span {
|
|
|
- width: 100%;
|
|
|
- padding: 5cpx;
|
|
|
- cursor: pointer;
|
|
|
- &:hover {
|
|
|
- background-color: $primary-color;
|
|
|
- color: $white-color;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-</style>
|
|
|
-<style lang="scss">
|
|
|
- .parent-operation-popover {
|
|
|
- padding: 0 !important;
|
|
|
- }
|
|
|
-</style>
|