欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 游戏 > 弹窗全局化

弹窗全局化

2025/2/21 3:25:34 来源:https://blog.csdn.net/m0_72030584/article/details/143918005  浏览:    关键词:弹窗全局化

效果:

初始的目录:

目录结构

src/

├── router/

│   ├── index.js

├── store/

│   ├── test.js

├── views/

│   ├── model/

│   │   ├── Model.vue

│   ├── Home.vue

│   ├── About.vue
│   └── Settings.vue
├── App.vue
└── main.js

思路:

  1. 弹窗组件化显示状态由全局showPop 变量控制
  2. 动态路由与弹窗绑定当路由变化时,确保弹窗能够根据路径同步更新active激活项逻辑处理
// route-index.js
import { createRouter, createWebHistory } from 'vue-router'const routes = [{path: '/',name: 'home',component: () => import('@/views/Home.vue')},{path: '/about',name: 'about',component: () => import('@/views/About.vue')
},{path: '/settings',name: 'settings',component: () => import('@/views/Settings.vue')}
]const router = createRouter({history: createWebHistory(),routes
})export default router
// store-test.js
import { defineStore } from 'pinia'export const testStore = defineStore('test', {state: () => ({showPop: false}),actions: {setShowPop(IS) {this.showPop = IS}}
})
// Model.vue
<template><div class="no-mask-popup" v-draggable="onDragStop" v-if="popupShow":style="{ width: Width, minHeight: Height, top: Top, left: Left }"><div class="popup-head"><slot name="title"></slot><div class="popup-head-title">{{ Title }}</div><div class="popup-head-colse"><span class="icon-svg-close" @click.stop="closePopup">x</span></div></div><slot></slot></div>
</template><script setup name="Model">
import { ref, onMounted, watch } from 'vue'
// 接受参数
const props = defineProps({id: {type: String,required: true},Title: {type: String,default: ''},Width: {type: String,default: '50%'},Height: {type: String,default: '42%'},Top: {type: String,default: '15%'},Left: {type: String,default: '20%'},
})watch(() => props.Top,(newVal) => {Top.value = newVal;}
);watch(() => props.Left,(newVal) => {Left.value = newVal;}
);
// 记录弹窗位置
// 将 Top 和 Left 改为响应式数据
const Top = ref(props.Top);
const Left = ref(props.Left);
onMounted(() => {const savedTop = localStorage.getItem(`popupTop_${props.id}`);const savedLeft = localStorage.getItem(`popupLeft_${props.id}`);if (savedTop) Top.value = savedTop;if (savedLeft) Left.value = savedLeft;
});
const onDragStop = (newPosition) => {Top.value = newPosition.top;Left.value = newPosition.left;// 通知父组件更新位置emit('update-position', newPosition);
};
const popupShow = ref(false)
const showPopup = () => {popupShow.value = true;
};
const emit = defineEmits(['closePopup', 'update-position']);
const closePopup = () => {popupShow.value = false;// 保存当前的位置信息localStorage.setItem(`popupTop_${props.id}`, Top.value);localStorage.setItem(`popupLeft_${props.id}`, Left.value);emit('closePopup');
};defineExpose({ showPopup, closePopup, })
</script><style scoped>
.no-mask-popup {position: absolute;top: 15%;left: 20%;background-color: pink;padding: 1rem;.popup-head {display: flex;justify-content: space-between;align-items: center;padding: 0rem 0rem 24px 0rem;color: #fff;font-size: 1.2rem;font-weight: bold;cursor: move;}.popup-head-title {color: #fff;font-size: 20px;font-weight: 700;}.popup-head-colse {cursor: pointer;}.icon-svg-close {color: #fff;cursor: pointer;align-items: center;display: inline-flex;width: 1.5rem;height: 1.5rem;line-height: 1.5rem;justify-content: center;position: relative;font-size: 20px;font-weight: 700;}
}
</style>
//About.vue
<template><h1>Welcome to About Page</h1>
</template>
// Home.vue
<template><div><h1>欢迎来到首页</h1></div>
</template><script setup>
</script>
// Setting.vue
<template><div><h1>欢迎来到设置</h1></div>
</template><script setup>
</script>
// PopupInfo.vue
<template><PopupView ref="parentPopup" :id="'parentPopup'" :is="showPopup" @update-position="updatePosition" :Top="sharedTop":Left="sharedLeft" :Title="'父级弹窗'" @closePopup="onParentPopupClose"><p>这是父级弹窗的内容。</p><button @click="openChildPopup">打开子级弹窗</button></PopupView><!-- 子级弹窗 --><PopupView ref="childPopup" :id="'childPopup'" :Top="sharedTop" :Left="sharedLeft" @update-position="updatePosition":Title="'子级弹窗'" @closePopup="onChildPopupClose"><p>这是子级弹窗的内容。</p><button @click="closeChildPopup">关闭子级弹窗</button></PopupView>
</template><script setup>
import { ref, computed } from 'vue';
import PopupView from '@/views/model/Model.vue'; // 请根据实际路径调整
import { testStore } from "@/stores/test.js";
const teststore = testStore();
const parentPopup = ref(null);
const childPopup = ref(null);
// 共享的位置信息
const sharedTop = ref('15%');
const sharedLeft = ref('20%');
// 从 localStorage 中恢复位置
const savedTop = localStorage.getItem('popupTop_shared');
const savedLeft = localStorage.getItem('popupLeft_shared');
if (savedTop) sharedTop.value = savedTop;
if (savedLeft) sharedLeft.value = savedLeft;// 更新位置信息并保存到 localStorage
const updatePosition = (newPosition) => {sharedTop.value = newPosition.top;sharedLeft.value = newPosition.left;localStorage.setItem('popupTop_shared', sharedTop.value);localStorage.setItem('popupLeft_shared', sharedLeft.value);
};
const showPopup = computed(() => {if (parentPopup.value && teststore.showPop) {parentPopup.value.showPopup();}
});// 关闭父级弹窗的回调
const onParentPopupClose = () => {// 父级弹窗关闭后的逻辑localStorage.removeItem('popup-info')teststore.setShowPop(false)
};// 打开子级弹窗
const openChildPopup = () => {// 关闭父级弹窗(实际上是隐藏)parentPopup.value.closePopup();// 打开子级弹窗childPopup.value.showPopup();
};// 关闭子级弹窗的回调
const onChildPopupClose = () => {// 子级弹窗关闭后的逻辑// 重新打开父级弹窗,实现“返回上一级”功能parentPopup.value.showPopup();
};// 关闭子级弹窗
const closeChildPopup = () => {childPopup.value.closePopup();
};
</script>
// main.jsimport { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCN from 'element-plus/dist/locale/zh-cn.mjs'
import App from './App.vue'
import router from './router'import draggableDirective from '@/utils/draggable'const app = createApp(App)
app.directive('draggable', draggableDirective)
app.use(createPinia())
app.use(router)
app.use(ElementPlus, {locale: zhCN
})
app.mount('#app')
//utils- draggable.js
const draggableDirective = {beforeMount(el, binding) {const header = el.querySelector('.popup-head'); // 获取头部元素if (!header) return; // 如果没有找到头部元素,则不做任何处理header.onmousedown = (e) => {let offsetX = e.clientX - el.getBoundingClientRect().left;let offsetY = e.clientY - el.getBoundingClientRect().top;// 添加避免文本选中的样式document.body.style.userSelect = 'none';const parent = el.parentElement;const parentRect = parent.getBoundingClientRect();const elRect = el.getBoundingClientRect();document.onmousemove = function (e) {let newLeft = e.pageX - offsetX;let newTop = e.pageY - offsetY;// 限制拖动范围在父节点内if (newLeft < parentRect.left) newLeft = parentRect.left;if (newTop < parentRect.top) newTop = parentRect.top;if (newLeft + elRect.width > parentRect.right) newLeft = parentRect.right - elRect.width;if (newTop + elRect.height > parentRect.bottom) newTop = parentRect.bottom - elRect.height;el.style.position = 'absolute';el.style.left = newLeft - parentRect.left + 'px';el.style.top = newTop - parentRect.top + 'px';};document.onmouseup = function () {document.onmousemove = null;document.onmouseup = null;// 恢复文本选中document.body.style.userSelect = '';// 如果绑定的值是函数,则在拖拽结束时调用它if (typeof binding.value === 'function') {const newPosition = {top: el.style.top,left: el.style.left,};binding.value(newPosition);}};};},
};export default draggableDirective;
// app.vue
<template><div style="width: 100%;height: 100vh;"><!-- 顶部菜单 --><div class="header"><div class="left-wrapper">我的系统</div><div class="center-wrapper"><el-menu :default-active="activeMenu" mode="horizontal" class="header-menu"><template v-for="item in menuList" :key="item.name"><!-- 有子菜单 --><el-sub-menu v-if="item.children" :index="item.name"><template #title>{{ item.title }}</template><el-menu-item v-for="subItem in item.children" :key="subItem.name" :index="subItem.name"@click="handleMenuClick(subItem.name)">{{ subItem.title }}</el-menu-item></el-sub-menu><!-- 无子菜单 --><el-menu-item v-else :index="item.name" @click="MenuClick(item.name)">{{ item.title }}</el-menu-item></template></el-menu></div><div class="right-wrapper">闹着玩</div></div><router-view /><PopupInfo></PopupInfo></div></template><script setup>
import { testStore } from "@/stores/test.js";
const test = testStore();
import PopupInfo from "@/views/PopupInfo.vue";
import { nextTick, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";const router = useRouter();
const route = useRoute();const menuList = [{ name: "home", title: "首页" },{ name: "about", title: "关于" },{name: "popup",title: "弹窗",children: [{ name: "popup-info", title: "信息弹窗" },{ name: "popup-alert", title: "警告弹窗" },],},{ name: "settings", title: "系统设置" },
];// 绑定当前激活菜单
const activeMenu = ref(route.name);// 路由变化时更新激活菜单项
watch(route, (newRoute) => {activeMenu.value = newRoute.name;
});const handleMenuClick = (name) => {console.log("点击了菜单项:", name);activeMenu.value = null;nextTick(() => {activeMenu.value = route.name;if (name === 'popup-info') {localStorage.setItem('popup-info', 'true')test.setShowPop(true)return;}});};
const MenuClick = (name) => {router.push({ name })
}
</script><style scoped>
.header {display: flex;align-items: center;justify-content: space-between;background: #2c3e50;color: white;padding: 0 20px;height: 60px;
}.left-wrapper {font-size: 24px;font-weight: bold;
}.center-wrapper {flex: 1;display: flex;margin: 0 20px;}:deep(.el-menu--horizontal>.el-sub-menu .el-sub-menu__title) {color: #fff;
}:deep(.el-menu--horizontal>.el-sub-menu .el-sub-menu__title:hover) {background: #409eff !important;color: #ffd700 !important;
}:deep(.el-menu--horizontal>.el-sub-menu.is-active .el-sub-menu__title) {border-bottom: 2px solid red;
}.el-menu--horizontal>.el-menu-item {color: #fff !important;
}.right-wrapper {font-size: 16px;
}.header-menu {width: 100%;background-color: #2c3e50;color: white;
}:deep(.el-menu-item.is-active) {color: red !important;/* 激活项文字红色 */font-weight: bold;border-bottom: 2px solid red;/* 添加激活项下划线 */
}:deep(.el-menu-item:hover) {background: #409eff !important;color: #ffd700 !important;/* 悬停文字金色 */
}
</style>

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词