Vue中自定义指令:ClickOutside(点击当前模块外的位置)

news2025/3/11 22:58:16

应用场景

假设我们有一个下拉框组件,当下拉框展开的时候,点击下拉框之外的元素可以自动关闭下拉框。

一 ClickOutside代码示例

在vue3中使用ClickOutside

// 导入自定义指令
import { ClickOutside as vClickOutside } from 'element-plus';

// 绑定指令触发对应事件
<div id="d1" v-click-outside="closeOrder">
    暂无数据
</div>

// 写相关逻辑事件
const closeOrder = () => {
    console.log('点击了d1区域之外的位置')
}

id="d1"记得写,否则不生效



vue2中使用ClickOutside

// 导入指令
import { ClickOutside } from 'element-plus';

// 映射该指令
directives:{ ClickOutside }

// 绑定指令触发对应事件
<div id="d1" v-clickOutside="closeOrder">
    暂无数据
</div>

// 写相关逻辑事件
closeOrder(){
    console.log('点击了d1区域之外的位置')
}

案例

<template>
  <div ref="dropdownRef" class="drop_down" id="d1" v-click-outside="closeOrder">
    <div class="cascade-input" @click="toggleDropdown" :class="{ 'is-active': isDropdownVisible }">
      <span class="cascade_choose">请选择</span>
      <el-icon :class="{ 'is-rotate': isDropdownVisible }" class="down"><ArrowDown /></el-icon>
    </div>
    <div class="cascade-dropdown" v-if="isDropdownVisible">
      <div style="display: flex">
        <!-- 一级菜单 -->
        <ul>
          <li
            class="cascade_item"
            v-for="(item, index) in cascadeType"
            :key="index"
            @click="chooseType(item.field)"
            :class="{ active: chooseTypeIndex == item.field }"
          >
            <span class="cascade_item_name">{{ item.name }}</span
            ><span class="cascade_item_name">
              <el-icon><ArrowRight /></el-icon>
            </span>
          </li>
        </ul>
        <!-- 二级菜单 -->
        <div class="choose" v-show="isShowTwoMenu">
          <div class="choose_header">
            <ul>
              <li
                style="cursor: pointer"
                v-for="(item, index) in MustList"
                @click="changeMustTab(item.id)"
                :key="index"
                :class="{ 'is-must': isMust == item.id }"
                >{{ item.name }}</li
              >
            </ul>
          </div>
          <div class="choose_content">
            <ul>
              <template v-for="item in secondList" :key="item.id">
                <li
                  v-if="!item.subImportField"
                  style="cursor: pointer"
                  @click="changeItem(item.id)"
                  :class="{ 'is-choose': isChooseItem == item.id }"
                  >{{ item.name }}</li
                >
                <template v-if="item.subImportField?.length > 0">
                  <div class="links">以下联系方式至少选一种</div>
                  <li
                    v-for="i in item.subImportField"
                    :key="i.id"
                    style="cursor: pointer"
                    @click="changeItem(i.id)"
                    :class="{ 'is-choose': isChooseItem == i.id }"
                    >{{ i.name }}</li
                  >
                </template>
              </template>
            </ul>
          </div>
          <div class="choose_footer"> 新增自定义字段 </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ArrowDown, ArrowRight } from "@element-plus/icons-vue"
import { ClickOutside as vClickOutside } from "element-plus"
/**
 * 切换
 */
const isDropdownVisible = ref(false)

const cascadeType = ref([
  {
    field: 1,
    name: "客户"
  },
  {
    field: 2,
    name: "联系人"
  },
  {
    field: 3,
    name: "跟进记录"
  }
])
const toggleDropdown = () => {
  // debugger
  isDropdownVisible.value = !isDropdownVisible.value
  console.log("isDropdownVisible.value", isDropdownVisible.value)
  if (!isDropdownVisible.value) {
    isShowTwoMenu.value = false
    chooseTypeIndex.value = null
  }
}

const closeOrder = () => {
  console.log("点击了d1区域之外的位置")
  isDropdownVisible.value = false
}

// const dropdownRef = ref(null)
// const handleClickOutside = (event) => {
//   if (dropdownRef.value && !dropdownRef.value.contains(event.target)) {
//     isDropdownVisible.value = false
//   }
// }

// onMounted(() => {
//   debugger
//   document.addEventListener("click", handleClickOutside)
// })

// onBeforeUnmount(() => {
//   document.removeEventListener("click", handleClickOutside)
// })

//切换类型
const chooseTypeIndex = ref<any>(null)
//展示二级菜单
const isShowTwoMenu = ref(false)

const secondList = ref<any>([])
// const secondListByNoMust = ref<any>([])
const chooseType = (field: any) => {
  chooseTypeIndex.value = field
  isShowTwoMenu.value = true
  isMust.value = 1
  // 获取二级菜单数据
  switch (field) {
    case 1:
      secondList.value = [
        {
          name: "称谓",
          id: 111,
          type: 0
        },
        {
          name: "生日",
          id: 22,
          type: 0
        },
        {
          name: "联系方式",
          id: 23,
          type: 1,
          subImportField: [
            {
              name: "邮箱",
              id: 24
            },
            {
              name: "手机",
              id: 25
            }
          ]
        }
      ]
      break
    case 2:
      secondList.value = [
        {
          name: "传真",
          id: 111,
          type: 0
        },
        {
          name: "职务",
          id: 22,
          type: 0
        }
      ]
      break
  }
}

const isChooseItem = ref("")
const changeItem = (id: any) => {
  isChooseItem.value = id
}

// 切换必须非必选
const isMust = ref(1)
const MustList = ref([
  {
    name: "必须",
    id: 1
  },
  {
    name: "非必须",
    id: 2
  }
])
const changeMustTab = (id) => {
  isMust.value = id
  isChooseItem.value = ""
  switch (id) {
    case 1:
      secondList.value = [
        {
          name: "称谓",
          id: 111,
          type: 0
        },
        {
          name: "生日",
          id: 22,
          type: 0
        },
        {
          name: "联系方式",
          id: 23,
          type: 1,
          subImportField: [
            {
              name: "邮箱",
              id: 24
            },
            {
              name: "手机",
              id: 25
            }
          ]
        }
      ]
      break
    case 2:
      secondList.value = [
        {
          name: "传真",
          id: 111,
          type: 0
        },
        {
          name: "职务",
          id: 22,
          type: 0
        }
      ]
      break
  }
}
</script>
<style scoped lang="scss">
.drop_down {
  position: relative;
  .cascade-input {
    height: 40px;
    padding: 0px 12px;
    border: 1px solid #ccc;
    border-radius: 4px;
    cursor: pointer;
    user-select: none;
    display: flex;
    justify-content: space-between;
    align-items: center;
    position: relative;
    .cascade_choose {
      color: #333333;
      font-size: 16px;
      font-weight: 400;
    }
  }
  .cascade-input.is-active {
    border-color: #409eff;
  }
  .down {
    transition: all 0.4s;
  }
  .is-rotate {
    transform: rotate(180deg);
  }
  .cascade-dropdown {
    position: absolute;
    top: 40px;
    left: 0px;
    z-index: 9999;
    display: inline-block;
    box-shadow: 0 2px 9px 0 #c8c9cc80;
    height: 291px;
    background: #fff;
    border-radius: 2px;
    border: 1px solid #ebedf0;
    border-radius: 4px;
    margin-top: 4px;
    .active {
      background: #f7f8fa;
    }
    .cascade_item {
      width: 240px;
      height: 36px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 0px 12px;
      cursor: pointer;
      .cascade_item_name {
        color: #323233;
        font-size: 14px;
        font-weight: 400;
      }
    }
    .choose {
      width: 240px;
      height: 291px;
      border-left: 1px solid #dcdfe6;
      position: relative;
      .choose_header {
        width: 100%;
        ul {
          display: flex;
          height: 36px;
          line-height: 36px;
          border-bottom: 1px solid #dedede;
          box-sizing: border-box;
          padding-left: 10px;
          li {
            margin-right: 20px;
            color: #323233;
            font-size: 14px;
            font-weight: 500;
            &:hover {
              cursor: pointer;
            }
          }
        }
        .is-must {
          color: #477fff;
          border-bottom: 1px solid #477fff;
        }
      }
      .choose_content {
        // padding-left: 12px;
        ul {
          li {
            height: 32px;
            line-height: 32px;
            padding-left: 12px;
          }
          .is-choose {
            background: #f7f8fa;
          }
        }
        .links {
          color: #477fff;
          font-size: 12px;
          font-family: "苹方";
          padding: 5px 0px 5px 12px;
        }
      }
      .choose_footer {
        position: absolute;
        left: 0;
        bottom: 0;
        width: 92%;
        height: 40px;
        line-height: 40px;
        text-align: center;
        border-top: 1px solid #dcdfe6;
        color: #477fff;
        font-size: 14px;
        font-weight: 400;
        margin: 0px 10px;
      }
    }
  }
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2313427.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2019年蓝桥杯第十届CC++大学B组真题及代码

目录 1A&#xff1a;组队&#xff08;填空5分_手算&#xff09; 2B&#xff1a;年号字符&#xff08;填空5分_进制&#xff09; 3C&#xff1a;数列求值&#xff08;填空10分_枚举&#xff09; 4D&#xff1a;数的分解&#xff08;填空10分&#xff09; 5E&#xff1a;迷宫…

jdk-21_linux-x64_bin.tar.gz Linux jdk21压缩包安装保姆级(详细安装教程)

jdk-21_linux-x64_bin.tar.gz 解压版详细安装教程 一、简洁版&#xff08;需要对 Linux 操作有一定基础&#xff09;二、图文详细教程1、前置准备2、解压安装3、配置环境变量4、验证成功 官网下载地址&#xff1a; https://www.oracle.com/java/technologies/downloads/#java2…

第6章 定时器计数器

目录 6.1 定时计数器的结构框图 6.2 定时器的控制字 6.2.1 TMOD&#xff1a;工作方式控制寄存器 6.2.2 定时/计数器控制寄存器TCON 6.3 定时/计数器的4种工作方式 6.3.1 方式0、方式1&#xff08;13位、16位定时计数方式&#xff09; 6.3.2 方式2(常数自动重装入) 6.3.3 方…

回归预测 | Matlab实现GWO-BP-Adaboost基于灰狼算法优化BP神经网络结合Adaboost思想的回归预测

回归预测 | Matlab实现GWO-BP-Adaboost基于灰狼算法优化BP神经网络结合Adaboost思想的回归预测 目录 回归预测 | Matlab实现GWO-BP-Adaboost基于灰狼算法优化BP神经网络结合Adaboost思想的回归预测回归效果基本介绍GWO-BP-Adaboost:基于灰狼算法优化BP神经网络结合Adaboost思想…

蓝桥杯真题0团建dfs+哈希表/邻接表

dfs邻接表储存或者哈希表的运用&#xff0c;考察我们对数据的存储 本题核心就是在求从根节点开始的两棵树相同的最长序列&#xff0c;首先确定用dfs进行深搜&#xff0c;对于节点的形式可以用邻接表&#xff0c;邻接矩阵&#xff0c;哈希表来进行存储数据。下面看代码 邻接表 …

系统架构的评估的系统的质量属性

体系结构苹果可以针对一个体系结构&#xff0c;也可以针对一组体系结构。 体系结构评估过程中&#xff0c;评估人员所关注的是系统的质量属性&#xff0c;所有评估方法所普遍关注的质量属性有以下几个&#xff1a;性能、可靠性&#xff08;容错&#xff0c;健壮性&#xff09;…

论文阅读:基于超图高阶表示的WSI生存预测

Generating Hypergraph-Based High-Order Representations of Whole-Slide Histopathological Images for Survival Prediction 文章目录 论文介绍快速阅读摘要1 引言2 相关工作2.1 生存分析2.2 超图学习的准备工作 3 方法3.1 patch采样和低级特征提取3.2 多超图学习3.2.1 多超…

27. Harmonyos Next仿uv-ui 组件NumberBox 步进器组件禁用状态

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; 文章目录 1. 组件介绍2. 效果展示3. 禁用状态设置3.1 整体禁用3.2 输入框禁用3.3 长按禁用 4. 完整示例代码5. 知识点讲解5.1 禁用状态属性5.2 禁用…

docker无法pull镜像问题解决for win10

docker无法pull镜像问题解决for win10 问题原因分析解决方法 问题 在win10系统上安装好doker-desktop后ping registry-1.docker.io不同&#xff0c;并且也无法登陆hub.docker.com, 使用docker pull xx也无法正常下载 原因分析 hub.docker.com在2024年5月之后&#xff0c;国内…

批量将 Excel 转换 PDF/Word/CSV以及图片等其它格式

Excel 格式转换是我们工作过程当中非常常见的一个需求&#xff0c;我们通常需要将 Excel 转换为其他各种各样的格式。比如将 Excel 转换为 PDF、比如说将 Excel 转换为 Word、再比如说将 Excel文档转换为图片等等。 这些操作对我们来讲都不难&#xff0c;因为我们通过 Office 都…

网络安全之RSA算法

1978年就出现了这种算法&#xff0c;它是第一个既能用于数据加密也能用于数字签名的算法。它易于理解和操作&#xff0c;也很流行。算法的名字以发明者的名字&#xff08;RonRivest&#xff0c;AdiShamir和LeonardAdleman&#xff09;命名。但RSA的安全性一直未能得到理论上的证…

Unity Dots

文章目录 什么是DotsDOTS的优势ECS&#xff08;实体组件系统&#xff09;Job System作业系统Burst编译器最后 什么是Dots DOTS&#xff08;Data-Oriented Technology Stack&#xff09;是Unity推出的一种用于开发高性能游戏和应用的数据导向技术栈&#xff0c;包含三大核心组件…

设计模式-结构型模式-桥接模式

概述 桥接模式 &#xff1a;Bridge Pattern&#xff1a; 是一种结构型设计模式。 旨在将抽象部分与实现部分分离&#xff0c;使它们可以独立变化。 它通过组合代替继承&#xff0c;解决类爆炸问题&#xff0c;并提高系统的灵活性和可扩展性。 组成部分 【抽象部分】&#xff08…

Ultravox:融合whisper+llama实现audio2text交互

Ultravox是由Fixie AI开发的一种创新型多模态大语言模型,专为实时语音交互设计。与传统的语音交互系统不同,Ultravox无需单独的语音识别(ASR)阶段,可以直接理解文本和人类语音,实现更快速、更自然的交互体验。Ultravox v0.5在语音理解基准测试中超越了OpenAI的GPT-4o Realt…

clickhouse集群部署保姆级教程

ClickHouse安装 版本要求 23.8及之后的版本 硬件要求 三台机器 建议配置 磁盘 ssd 500G内存 32gcpu 16c 最低配置 磁盘 机械硬盘 50G内存 4gcpu 4c 容量规划 一亿条数据大约使用1TB磁盘容量 参考官方容量推荐 安装包准备 zookeeper安装 zookeeper需要java启动&…

驾培市场与低空经济无人机融合技术详解

随着科技的飞速发展和社会的不断进步&#xff0c;驾培市场正面临着前所未有的变革。传统汽车驾驶培训已不再是唯一的选择&#xff0c;无人机驾驶等新兴领域正逐渐成为驾培市场的重要组成部分。本报告旨在探讨驾培市场与低空经济的融合发展&#xff0c;特别是应用型人才培养与驾…

简单记录一下Oracle数据库与mysql数据库注入的不同。

Oracle数据库的注入比mysql较复制。 一确定注入点&#xff1a;与mysql一样。 and 11 -- #文章有出现. and 12 -- #文章不见了。 二。确定列数。 ’order by 1&#xff0c;2 -- #没问题 order by 1,2,3 -- #保错&#xff0c;所以有两列。 三&#xff0c;所有uni…

如何将本地已有的仓库上传到gitee (使用UGit)

1、登录Gitee。 2、点击个人头像旁边的加号&#xff0c;选择新建仓库&#xff1a; 3、填写仓库相关信息 4、复制Gitee仓库的地址 5、绑定我们的本地仓库与远程仓库 6、将本地仓库发布&#xff08;推送&#xff09;到远程仓库&#xff1a; 注意到此处报错&#xff…

Day04 模拟原生开发app过程 Androidstudio+逍遥模拟器

1、用Androidstudio打开已经写好了的music项目 2、逍遥模拟器打开apk后缀文件 3、在源文件搜索关键字 以后的测试中做资产收集

若依ry-vue分离板(完整版)前后端部署

目录 1.目标 2.准备工作 3.源码下载 4.整理前后端目录 5.先部署后端 &#xff08;1&#xff09;导入数据库 &#xff08;2&#xff09;改代码数据库配置 &#xff08;3&#xff09;运行redis &#xff08;4&#xff09;运行执行文件 &#xff08;5&#xff09;后端启…