首页 > 其他分享 >弹窗全局化

弹窗全局化

时间:2024-11-22 12:45:19浏览次数:3  
标签:el vue const value import 全局 弹窗

效果:

初始的目录:

目录结构

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.js

import { 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>

标签:el,vue,const,value,import,全局,弹窗
From: https://blog.csdn.net/m0_72030584/article/details/143918005

相关文章

  • 一段VUE代码:通过组件封装全局方法、自定义指令和注册组件
    index.js//插件定义第一种方式,对象:拥有install()方法的对象constmyPlugin={install(app,options){//配置全局方法app.config.globalProperties.globalMethod=function(value){returnvalue.toLowerCase();};//注册全局组件ap......
  • 从“配不平”到全局优化:集成计划助力食品饮料行业年度经营计划
    时间进入11月,很多企业的年度经营计划和预算制定正在如火如荼地进行中。有些企业甚至从9月便已开始,这不仅体现了这项计划的重要性,也反映了其复杂程度之高——往往需要两三个月的时间,企业几乎所有部门众多人员的卷入。涉及的数据和信息也非常繁杂,流程上则需要反复核对处理、同步......
  • BUG: udp的"addrlen"由局部变量改为全局变量,udp的数据包就无法发送到目标地址。
    一.BUG描述项目上要用到LWIP的UDP协议传输数据,然后弄了一个了UDP的demo;跑通了之后就对这个demo重新封装。我把套接字长度变量(addrlen)由局部改为全局之后,服务器的UDP就只能接收,不能发送数据了。二.BUG原因点击查看代码/**sockfd:套接字文件描述符*buf:接收缓冲区*len:接收......
  • IDEA如何设置编码格式,字符编码,全局编码和项目编码格式
    前言大家好,我是小徐啊。我们在开发Java项目(Springboot)的时候,一般都是会设置好对应的编码格式的。如果设置的不恰当,容易造成乱码的问题,这是要避免的。今天,小徐就来介绍下我们如何在IDEA中设置项目的编码格式,文件的编码格式等。如何设置编码格式首先,我们点击下文件,然后再点击下设......
  • 全局统一返回结果类
    packagecom.atguigu.daijia.common.result;importlombok.Data;/***全局统一返回结果类**/@DatapublicclassResult<T>{//返回码privateIntegercode;//返回消息privateStringmessage;//返回数据privateTdata;publi......
  • 2024-11-17 uniapp小程序之自定义 · 全局弹窗
    效果图:目录结构: 共需要修改6个地方,开始前请安装一个依赖:vue-inset-loadernpmivue-inset-loadervue-inset-loader的GitHub地址:https://github.com/1977474741/vue-inset-loader一:新建弹窗文件components/golbalModa.vue<template><viewclass="modal"v-if="globa......
  • SFMA(提取全局和局部特征 并进行简单的融合)
    importtorchimporttorch.nnasnnimporttorch.nn.functionalasFclassDMlp(nn.Module):'''用来提取局部特征'''def__init__(self,dim,growth_rate=2.0):super().__init__()hidden_dim=int(dim*......
  • WPF如何全局应用黑白主题效果
    灰白色很多时候用于纪念,哀悼等。那么使用WPF如何来做到这种效果呢?要实现的这种效果,我们会发现,它其实不仅仅是要针对图片,而是要针对整个窗口来实现灰白色。如果只是针对图片的话,我可以可以对图片进行灰阶转换,即可达到灰色效果。以下是图片转灰阶的代码,当然方法不仅仅是这一种......
  • go fiber:全局中间件添加排除的path
    一,代码:全局中间件对所有的api生效,如果有个别不想应用全局中间件的api,则需要从代码中进行排除:例如:支付宝或微信的回调接口packagemiddlewareimport( "fmt" "github.com/gofiber/fiber/v2""regexp")funcApiSign(c*fiber.Ctx)error{//得到当前url ......
  • go fiber: 多个全局中间件的执行顺序
    一,代码:middleware.gopackagemiddlewareimport( "fmt" "github.com/gofiber/fiber/v2")funcMiddle1(c*fiber.Ctx)error{ fmt.Println("middle1before") err:=c.Next() fmt.Println("middle1after") returnerr}......