Vue组件-检测外部点击

Vue component - detect outside click

所以,我已经在 Vue JS (2.x) 中实现了这个自定义下拉列表,并且几乎有我需要的东西,除了列表打开后,我想在组件外部单击 ( parent 页面或组件上的任意位置)关闭列表。我尝试捕获我的组件的根 divblur 事件,但可以理解它不起作用,因为 divs 没有获得焦点,因此无法模糊。所以此时此刻,解决方案似乎是——能够在组件外部监听点击事件。那可能吗? child 可以在 Vue 中监听其 parent 上的事件吗?如果或者即使不是,什么是最好的 and/or 实现此行为的最简单方法?

这是我在 CodeSandbox 中的代码,为了方便起见,我还在下面复制它 - https://codesandbox.io/s/romantic-night-ot7i8

Dropdown.vue

<template>
  <div class="main-container" @blur="listOpen = false">
    <button class="sel-btn" @click="listOpen = !listOpen">
      {{ getText() }}
    </button>
    <br />
    <div class="list-items" v-show="listOpen">
      <button
        class="item"
        v-for="(l, i) in list"
        :key="i"
        @click="btnClicked(i)"
      >
        {{ l }}
      </button>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    defaultText: {
      type: String,
      required: false,
    },
    list: {
      type: Array,
      required: true,
    },
  },
  data() {
    return {
      selectedIndex: -1,
      listOpen: false,
    };
  },

  methods: {
    getText() {
      if (this.selectedIndex !== -1) return this.list[this.selectedIndex];
      if (this.defaultText) return this.defaultText;
      return "Please Select";
    },

    btnClicked(index) {
      this.selectedIndex = index;
      this.listOpen = false;
      this.$emit("input", this.list[index]);
    },
  },
};
</script>

<style scoped>
.main-container {
  position: relative;
  display: inline-block;
  min-width: 125px;
  max-width: 125px;
  text-align: left;
  border: 0;
  background: #0000;
}

.sel-btn {
  display: inline-block;
  width: 100%;
  text-align: left;
  border: 0;
  background: linear-gradient(0deg, #efefef, #fff);
  padding: 8px;
  border: 1px solid #1111;
  box-shadow: 1px 1px 1px #1115;
  cursor: pointer;
  overflow: hidden;
}

.sel-btn:hover {
  background: linear-gradient(0deg, #cef, #fff);
  box-shadow: 1px 1px 1px #6390bd;
  color: #58a;
}

.sel-btn::after {
  content: "bc6";
  position: absolute;
  right: 4%;
}

.list-items {
  position: absolute;
  width: 100%;
  background: #fff;
  box-shadow: 1px 1px 3px #3333;
}

.item {
  display: block;
  width: 100%;
  text-align: left;
  background: #fff;
  border: 0;
  cursor: pointer;
  padding: 8px;
  border-bottom: 1px solid #2221;
  font-size: 0.8rem;
}

.item:hover {
  background: #4b84c5;
  color: #fff;
}
</style>

App.vue

<template>
  <div id="app">
    <br />
    <div style="margin-top: 30px; margin-bottom: 10px">
      <Dropdown
        :list="regions"
        v-model="selectedRegion"
        style="margin-right: 10px"
        defaultText="--Select Region--"
      ></Dropdown>
      <Dropdown
        :list="cities"
        v-model="selectedCity"
        defaultText="--Select City--"
      ></Dropdown>
    </div>
    <div style="padding: 30px; background: #27e2">
      (This poem excerpt is here to act as a filler)
      <h2>The Daffodils</h2>
      <h4><i>William Wordsworth</i></h4>
      I wondered lonely<br />
      As a cloud<br />
      That floats on high<br />
      O'er vales and hills<br />
      When all at once<br />
      I saw a crowd<br />
      A host<br />
      Of golden daffodils.<br />
    </div>
    <div style="padding: 30px">
      <b>Region Selected:</b>&nbsp; {{ selectedRegion }}<br />
      <b>City Selected:</b>&nbsp; {{ selectedCity }}
    </div>
  </div>
</template>

<script>
import Dropdown from "./components/Dropdown";

export default {
  name: "App",
  components: {
    Dropdown,
  },

  data() {
    return {
      set: false,
      regions: ["California", "Nova Scotia", "Kerala", "Bavaria", "Queensland"],
      cities: [
        "Munich",
        "San Diego",
        "Paris",
        "Prague",
        "Copenhagen",
        "Kolkata",
        "Dhaka",
        "Colombo",
        "Little City",
      ],
      selectedRegion: "",
      selectedCity: "",
    };
  },
};
</script>

<style>
* {
  font-family: "Segoe UI", Helvetica, Arial, sans-serif;
}

h1,
h2,
h3,
h4,
b {
  font-weight: 400;
  color: #27c;
}
</style>

好的...到目前为止,这不是最佳解决方案,但它是一个可行的解决方案。 我使用了我所有的 MacGyver 能力并找到了一个方法。

请检查这个CodeSandbox

我所做的只是使用您的 listOpen 并添加了一个 eventListner。我发现您的自定义下拉列表没有内置 @blur,因为它不是输入 ofc。 所以我在 mounted 挂钩中为它添加了一个事件。

关键是我还在 100 毫秒上添加了一个 setTimeout,否则你无法 select 下拉列表中的任何项目,下拉列表将以 blur 更快地关闭,然后你能够select任何东西。

最好的方法是使用 Vue Custom Directive 将焦点功能挂接到您的组件,并在组件内部或外部发生点击时将事件传播到组件外部。

在下面的示例中,我们创建了 v-ctrlfocus 指令,它将挂接到组件的根目录并为整个 window.

创建一个点击事件监听器

这样,当点击事件被触发时,我们可以比较它是发生在组件内部还是外部。

如果它发生在内部,我们将触发一个 focusin 事件,您可以使用 @focusin 在组件外部收听该事件。相反,如果点击发生在组件外部,它将触发 focusout 事件。

为了使用 Vue 新玩具进行更多更新,我使用了 typescript 和