Vue.js 带有 v-model 的自定义 select 组件

Vue.js custom select component with v-model

我想在 Vue.js 中创建自定义 select 组件。由于我需要特定的选项样式,我需要创建由 div 等组成的 'select',其外观和行为都像真正的 html select。

目前我有这样的东西:

Vue.component('child', {
  template: `<div class="component-container"  @click="showOptions = !showOptions">
        <div class="component__select">
            <span class="component__select--name">Select Fruit</span>
            
            <span class="c-arrow-down" v-if="!showOptions"></span>
            <span class="c-arrow-up" v-if="showOptions"></span>
        </div>
        <ul class="component__select-options" v-if="showOptions" >
            <li class="select--option" v-for="option in options">
                <label> <input type="checkbox" :value="option"/> {{option.name}}</label>
            </li>
        </ul>
    </div>`,

  methods: {
    selectOption(option) {
      this.$emit('option', option)
    }
  },
  data: () => ({
  showOptions: false,
  }),
  props: ['options']
});

var vm = new Vue({
  el: '#app',
  data: () => ({
 options: [
  {id: 0, name: 'Apple'},
  {id: 1, name: 'Banana'},
  {id: 2, name: 'Orange'},
  {id: 2, name: 'Strawberry'},
  ],
  selectedFruit: ''
  }),
})
 .component__select {
   height: 38px;
   background-color: #F5F7FA;
   border: 1px solid #dddddd;
   line-height: 38px;
   display: grid;
   max-width: 500px;
   grid-template-columns: 10fr 1fr;
 }

 .component__select--name {
   font-size: 0.8rem;
   padding: 0 0 0 25px;
   cursor: pointer;
 }

 .c-arrow-down {
   justify-self: end;
 }

 .component__select-options {
   max-height: 180px;
   border: 1px solid #dddddd;
   border-top: none;
   overflow: auto;
   position: absolute;
   z-index: 1500;
   max-width: 500px;
   width: 500px;
   margin: 0;
   padding: 0;
 }

 .select--option {
   height: 35px;
   display: grid;
   align-content: center;
   padding: 0 0 0 25px;
   background-color: #f5f5fa;
   border-bottom: 1px solid #dddddd;
 }

 .select--option:last-child {
   border-bottom: none;
 }

 .select--option:nth-child(2n) {
   background-color: #ffffff;
 }
 
 .select--option input{
   display: none;
 }

 .single-option {
   height: 55px;
   background-color: #2595ec;
   font-size: 0.8rem;
   border: 1px solid red;
 }

 .cust-sel {
   width: 200px;
   height: 38px;
   background-color: #f5f5fa;
   border: 1px solid #dddddd;
 }

 .cust-sel:focus {
   outline-width: 0;
 }
<html>
<head>
  <title>An example</title>
</head>
<body>
  <div id="app">
    <span> This is parent component</span>
    <p>I want to have data from select here:  "{{selectedFruit}}"</p>
    <child :options="options" v-model="selectedFruit"></child>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</body>
</html>

但我现在的问题是如何在子组件上使用 v-model return 从子组件到父组件的数据。

(我知道我可以从子组件发出数据并执行:
<custom-select :options="someOptions" @selected="setSelectedOption"/>
但我需要它是可重用的,并且编写越来越多的方法来从父组件中的每个 select 检索数据并不是我认为它应该如何工作。)

我还需要 return 整个对象,而不仅仅是 ID。 (这就是为什么我有 :value="option") 有任何想法吗?

在自定义组件上使用 v-model 时,您只需要声明一个名为 'value' 的 prop,当您需要组件触发 'input' 事件时。

像这样:

<template>
  <form @submit.prevent="$emit('onSearch',val)" class="form-perfil">
    <div class="form-group col-md-12">
      <input v-model="val" @input="$emit('input',val)" 
      placeholder="filtrar resultados" class="form-control">
    </div>
  </form>
</template>
<script>
module.exports = {
  name: "CaixaFiltro",
  props: ["value"],
  data: _ => ({ val: "" }),
  created() {
    this.val = this.value
  }
}
</script>

然后你可以像这样使用(注册组件后):

<caixa-filtro v-model="textoBusca" @onSearch="listar"></caixa-filtro>

您可以找到更详细的信息there

作为 Vue Guide said:

v-model is essentially syntax sugar for updating data on user input events, plus special care for some edge cases.

语法糖如下:

指令=v-model 将绑定值,然后监听 input 事件以进行更改,如 v-bind:value="val" v-on:input="val = $event.target.value"

因此对于您的用例,您需要创建一个 prop=value,然后使用 event=input 发出选定的选项。

就像下面的演示(bind/emit 整个选项对象):

Vue.config.productionTip = false
Vue.component('child', {
  template: `<div class="component-container"  @click="showOptions = !showOptions">
        <div class="component__select">
            <span class="component__select--name">{{value ? value.name : 'Select Fruit'}}</span>
            
            <span class="c-arrow-down" v-if="!showOptions"></span>
            <span class="c-arrow-up" v-if="showOptions"></span>
        </div>
        <ul class="component__select-options" v-if="showOptions" >
            <li class="select--option" v-for="option in options" @click="selectOption(option)">
                <label> <input type="checkbox" :value="option"/> {{option.name}}</label>
            </li>
        </ul>
    </div>`,

  methods: {
    selectOption(option) {
      this.$emit('input', option)
    }
  },
  data: () => ({
   showOptions: false
  }),
  props: ['options', 'value']
});

var vm = new Vue({
  el: '#app',
  data: () => ({
 options: [
  {id: 0, name: 'Apple'},
  {id: 1, name: 'Banana'},
  {id: 2, name: 'Orange'},
  {id: 2, name: 'Strawberry'},
  ],
  selectedFruit: ''
  }),
})
 .component__select {
   height: 38px;
   background-color: #F5F7FA;
   border: 1px solid #dddddd;
   line-height: 38px;
   display: grid;
   max-width: 500px;
   grid-template-columns: 10fr 1fr;
 }

 .component__select--name {
   font-size: 0.8rem;
   padding: 0 0 0 25px;
   cursor: pointer;
 }

 .c-arrow-down {
   justify-self: end;
 }

 .component__select-options {
   max-height: 180px;
   border: 1px solid #dddddd;
   border-top: none;
   overflow: auto;
   position: absolute;
   z-index: 1500;
   max-width: 500px;
   width: 500px;
   margin: 0;
   padding: 0;
 }

 .select--option {
   height: 35px;
   display: grid;
   align-content: center;
   padding: 0 0 0 25px;
   background-color: #f5f5fa;
   border-bottom: 1px solid #dddddd;
 }

 .select--option:last-child {
   border-bottom: none;
 }

 .select--option:nth-child(2n) {
   background-color: #ffffff;
 }
 
 .select--option input{
   display: none;
 }

 .single-option {
   height: 55px;
   background-color: #2595ec;
   font-size: 0.8rem;
   border: 1px solid red;
 }

 .cust-sel {
   width: 200px;
   height: 38px;
   background-color: #f5f5fa;
   border: 1px solid #dddddd;
 }

 .cust-sel:focus {
   outline-width: 0;
 }
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<div id="app">
  <span> This is parent component</span>
  <p>I want to have data from select here:  "{{selectedFruit}}"</p>
  <child :options="options" v-model="selectedFruit"></child>
</div>