夜猫子的知识栈 夜猫子的知识栈
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

夜猫子

前端练习生
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《Web Api》
    • 《ES6教程》
    • 《Vue》
    • 《React》
    • 《TypeScript》
    • 《Git》
    • 《Uniapp》
    • 小程序笔记
    • 《Electron》
    • JS设计模式总结
  • 《前端架构》

    • 《微前端》
    • 《权限控制》
    • monorepo
  • 全栈项目

    • 任务管理日历
    • 无代码平台
    • 图书管理系统
  • HTML
  • CSS
  • Nodejs
  • Midway
  • Nest
  • MySql
  • 其他
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • Ajax
  • Vite
  • Vitest
  • Nuxt
  • UI库文章
  • Docker
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 任务管理日历

    • 前言
    • 前端搭建

      • 前端指南
      • 项目初始化
      • 封装日历组件
      • 实现拖拽
      • 实现弹窗
        • 基于 Teleport 实现 Popover 组件
        • 代码实现
          • 接收的 props
          • template
          • 实现思路
          • 单天事项
          • 跨天事项
          • 获取位置信息
          • 使用 Popover 组件
    • 后端搭建

    • 部署前置篇
    • 前端部署

    • 后端部署

  • 无代码平台

  • 图书管理系统

  • 《全栈项目》
  • 任务管理日历
  • 前端搭建
夜猫子
2023-04-06
目录

实现弹窗

# 弹窗组件 Popover 的实现

# 基于 Teleport 实现 Popover 组件

项目中,对于 Popover 我们是借助 Vue3 的 Teleport 组件自行封装的;

因为从使用场景上,我们需要的 Popover 组件和在其它 UI 组件的 Popover 还不太一样;比如

  1. 当点击一个事项,打开 Popover

在 UI 组件库中, Popover 是需要通过 slot 插槽去声明 trigger 作为触发元素,并根据

trigger 元素的 DOM 信息去确认 Popover 的位置信息的。通俗点来说就是点击在谁身上,就在谁身

上弹 Popover。

在我们的场景中,事项是存在跨天事项的,比如用户点击了 5号的一个事项,但可能这个事项跨了 1号到10

号,比如下图,所以通过 slot 插槽的方式去获取触发元素的位置信息就明显不合适了;

img

  1. 当我们打开事项并点击其它事项进行事项切换时;

在我们的场景中 Popover 是不关闭的,并且切换事项时还会有一个过渡动画;

而在 UI 组件库点击其它事项时,Popver 会先关闭,需要再次点击,才能打开;虽然我们能手动控制它的

显示隐藏,但是用手动控制进行切换时,就会丢过渡动画

基于以上原因,所以需要我们自己封装一个具有自己业务属性 Popover 组件

# 代码实现

# 接收的 props

const props = defineProps<{
  show: boolean;
  positionInfo: PopoverPosition | null;
  // 放置源的样式
  dropStyle: {
    width: number;
    height: number;
  };
  popoverStyle?: {
    width: number;
    height: number | "auto";
  };
  eventId?: number;
  overlap?: boolean;
  placement?: Placement;
  raw?: boolean; // 是否有基础样式,默认不带
}>();
type PopoverPosition = {
  start: PopoverTarget;
  end: PopoverTarget | null; // 如果是单天的事项,那么 end 为 null
  isRange: boolean;
};

type PopoverTarget = {
  time: string;
  dateIndex: number;
  target?: HTMLElement;
};

type Placement =
  | "right-start"
  | "left-start"
  | "right-end"
  | "left-end"
  | "top-start"
  | "top-end"
  | "bottom"
  | "bottom-start"
  | "bottom-end";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
show 受控模式下控制是否显示
positionInfo popover 弹出的位置信息
dropStyle 放置源的样式,此处就是 Day 组件的宽高信息
popoverStyle 自定义 Popover 的宽高
eventId 事件 eventId,用于判断点击是否是事项
overlap 弹出时,是否覆盖自身元素
placement 弹出位置
raw Popover 是否带默认样式,会带 padding

# template

<template>
  <!-- to 选择 #app 是因为可以使用 naive 组件库提供的样式信息 -->
  <Teleport to="#app">
    <Transition>
      <n-el
        v-if="show"
        tag="section"
        :class="[
          'absolute left-0 top-0 z-40 rounded transition transform  box-border',
          { 'px-3 py-2': raw },
        ]"
        :style="contentstyle"
        @click="handleClick"
      >
        <slot />
      </n-el>
    </Transition>
  </Teleport>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 实现思路

通过 positionInfo.isRange 判断是否是跨天事项

# 单天事项

  1. 如果 placement 通过 props 传递了,那么使用传递的 placement,否则根据 positionInfo.start.dateIndex 获取 placement 的值
  2. 通过调用 getBoundingClientRect 去获取 positionInfo.start.target 位置信息
  3. 根据 placement的值和 positionInfo.start.target 位置信息去求 Popover 的位置信息
let { placement } = getPopoverPlacement(start.dateIndex);
if (props.placement) {
  placement = props.placement;
}
const position = getPositionInfo(start.target as HTMLElement);

isLeft = placement.startsWith("left");
isRight = placement.startsWith("right");
isEnd = placement.endsWith("end");
isBottom = placement.startsWith("bottom");
isTop = placement.startsWith("top");

left = position?.left;
top = position?.top;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 跨天事项

跨天事项和单天事项,唯一不一样的点就是在获取 placement 值的时候不一样,因为单天事项两侧都是由冗余空间的,而跨天事项不一定,比如这种,只能让 Popover 向上或者向下弹

img

在代码中,我们专门对这种情况做了判断

// 判断事件的两侧是否有冗余空间,如果没有冗余空间,那么 popover 需要向上或者向下弹出
const isRedundantSpace = toLeft || toRight;

if (isRedundantSpace) {
  if (isEndRow) {
    // 如果左侧非末行,并且还有冗余空间,那么按照 left-start 处理
    const leftPlacement = isLeftEndRow ? "left-end" : "left-start";

    return {
      direction: toLeft ? "start" : "end",
      placement: toLeft ? leftPlacement : "right-end",
    };
  }
  return {
    direction: toLeft ? "start" : "end",
    placement: toLeft ? "left-start" : "right-start",
  };
} else {
  return {
    direction: "start",
    placement: startRowNum <= 2 ? "bottom-start" : "top-start",
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 获取位置信息

获取位置信息就是体力活了,根据 top、left、right、bottom、end 分别做不同方向的处理就好 img

# 使用 Popover 组件

在组件中使用 img

编辑 (opens new window)
上次更新: 2024/6/18 13:14:59
实现拖拽
后端指南

← 实现拖拽 后端指南→

最近更新
01
IoC 解决了什么痛点问题?
03-10
02
如何调试 Nest 项目
03-10
03
Provider注入对象
03-10
更多文章>
Copyright © 2019-2025 Study | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式