目录

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

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

应用场景

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

一 ClickOutside代码示例

在 中使用ClickOutside

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

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

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

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

中使用ClickOutside

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

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

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

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

案例

https://i-blog.csdnimg.cn/direct/1598ab6d8cd346fdbcc2db06c5d1b883.png

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