【问题标题】:Open a Vuetify dialog from a component template in VueJS从 VueJS 中的组件模板打开 Vuetify 对话框
【发布时间】:2018-06-10 15:28:09
【问题描述】:

我正在使用 VueJS Vuetify framework,我需要从另一个模板打开一个对话框 - 作为组件模板导入。单击 App.vue 中的 Menu 按钮 后,Modal 应该会打开。 这是我的设置:

  • App.vue = 带有菜单按钮的导航模板
  • Modal.vue = 模态模板,在 main.js 中作为全局导入

ma​​in.js

import Modal from './components/Modal.vue'
Vue.component('modal', Modal)

Modal.vue 模板:

<template>
  <v-layout row justify-center>
    <v-btn color="primary" dark @click.native.stop="dialog = true">Open Dialog</v-btn>
    <v-dialog v-model="dialog" max-width="290">
      <v-card>
        <v-card-title class="headline">Use Google's location service?</v-card-title>
        <v-card-text>Let Google help apps determine location. This means sending anonymous location data to Google, even when no apps are running.</v-card-text>
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn color="green darken-1" flat="flat" @click.native="dialog = false">Disagree</v-btn>
          <v-btn color="green darken-1" flat="flat" @click.native="dialog = false">Agree</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </v-layout>
</template>
<script>
  export default {
    data () {
      return {
        dialog: false
      }
    }
  }
</script>

如何打开对话框?

【问题讨论】:

    标签: javascript vuejs2 vue-component vuetify.js


    【解决方案1】:

    在您的 App.vue template 中添加此内容

    <modal></model>
    

    它将使用v-btnv-dialog 呈现您当前的Modal.vue 模板

    现在里面会有一个button - Open Dialog,当你点击那个模态框就会打开。

    【讨论】:

    • 我还需要从导航菜单中打开该模式。该怎么做?
    • 当你创建 v-model 时,你可以绑定 v-model,当你创建 v-model true 时,你的模型会出现,当你创建 false 时,它会隐藏。
    【解决方案2】:

    您可以使用自定义事件和event bus for non parent-child communication 打开对话框。

    如果您的应用程序变得有点复杂,我建议您使用Vuex for state management


    事件总线解决方案:

    在您的 ma​​in.js 或新文件中创建并导出新的 Vue 实例:

    export const bus = new Vue()

    app.vue 中导入 bus 并发出事件:

    <template>
      <div>
        <button @click.prevent="openMyDialog()">my button</button>
      </div>
    </template>
    
    <script>
      import {bus} from '../main' // import the bus from main.js or new file
      export default {
        methods: {
          openMyDialog () {
            bus.$emit('dialog', true) // emit the event to the bus
          }
        }
      }
    </script>
    

    modal.vue 中也导入总线并在 created 钩子中监听事件:

    <script>
      import {bus} from '../main'    
      export default {
        created () {
          var vm = this
          bus.$on('dialog', function (value) {
            vm.dialog = value
          })
        }
      }
    </script>
    

    【讨论】:

    • 我试过了,运行 npm run dev 后得到这个 _'bus' is not defined _ 错误:error in ./src/components/Modal.vue ✘ http://eslint.org/docs/rules/no-undef 'bus' is not defined src/components/Modal.vue:23:5 bus.$on('dialog', function (value) { ^ ✘ 1 problem (1 error, 0 warnings) Errors: 1 http://eslint.org/docs/rules/no-undef @ ./src/main.js 37:0-43 @ multi ./build/dev-client babel-polyfill ./src/main.js我错过了什么?
    • 哦,对不起!我更正了我的答案!当然,我们必须在“模块化”版本中导出和导入总线。为了不再把它搞砸,我还测试了代码:) 再次抱歉快速拍摄。
    • 感谢您的帮助! :) 出现错误,因为缺少数据部分。这是正确的/最佳实践修复吗? import {bus} from '../main' export default { data () { return { dialog: false } }, created () { var vm = this bus.$on('dialog', function (value) { vm.dialog = value }) } }再次感谢
    • 不客气 :) 很高兴为您提供帮助!您可以在官方文档中找到事件总线解决方案:vuejs.org/v2/guide/…。这里只适用于模块系统。所以我会说这是一个最佳实践。但它只适用于小型项目。我可以想象,由于您使用的是 vuetify,您的应用程序可能会变得更大一些。对于更大的应用程序,建议使用 Vuex:vuex.vuejs.org/en。这非常简单直接。这是我通常使用的。
    • 同理。您将在 card.vue 中发出一个事件并在 app.vue 中收听它。
    【解决方案3】:

    不需要事件总线和 v-model

    更新:

    当我第一次回答这个问题时,我将我的答案发布为“解决方法”,因为当时感觉它并不完全“正确”,而且我是 Vue.js 的新手。我想使用 v-model 指令打开或关闭对话框,但我无法到达那里。一段时间后,我使用 input 事件value 属性 找到了how to do this in the docs,这是我认为在没有事件总线的情况下应该如何完成的。

    父组件:

    <template>
       <v-btn color="accent" large @click.stop="showScheduleForm=true">    
       <ScheduleForm v-model="showScheduleForm" />
    </template>
    
    <script>
    import ScheduleForm from '~/components/ScheduleForm'
    
    export default {
      data () {
        return {
          showScheduleForm: false
        }
      },
      components: {
        ScheduleForm
      }
    }
    </script>
    

    子组件(ScheduleForm):

    <template>
    <v-dialog v-model="show" max-width="500px">
      <v-card>
        <v-card-actions>
          <v-btn color="primary" flat @click.stop="show=false">Close</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
    </template>
    
    <script>
    export default {
      props: {
         value: Boolean
      },
      computed: {
        show: {
          get () {
            return this.value
          },
          set (value) {
             this.$emit('input', value)
          }
        }
      }
    }
    </script>
    

    原答案:

    我能够在不需要全局事件总线的情况下解决这个问题。

    我使用了带有 getter 和 setter 的计算属性。由于 Vue 会警告您直接更改父属性,因此在 setter 中我只是向父级发出了一个事件。

    代码如下:

    父组件:

    <template>
       <v-btn color="accent" large @click.stop="showScheduleForm=true"></v-btn>   
       <ScheduleForm :visible="showScheduleForm" @close="showScheduleForm=false" />
    </template>
    
    <script>
    import ScheduleForm from '~/components/ScheduleForm'
    
    export default {
      data () {
        return {
          showScheduleForm: false
        }
      },
      components: {
        ScheduleForm
      }
    }
    </script>
    

    子组件(ScheduleForm):

    <template>
    <v-dialog v-model="show" max-width="500px">
      <v-card>
        <v-card-actions>
          <v-btn color="primary" flat @click.stop="show=false">Close</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
    </template>
    
    <script>
    export default {
      props: ['visible'],
      computed: {
        show: {
          get () {
            return this.visible
          },
          set (value) {
            if (!value) {
              this.$emit('close')
            }
          }
        }
      }
    }
    </script>
    

    【讨论】:

    • 我认为这是一个绝对完美的答案。谢谢你。这很完美!
    • 试过了,它可以根据需要工作。最好把它推到顶部!
    • 如果上述方法不起作用,请将您孩子的计算属性更改为使用 $attrs,如下所示:show: { get () { return this.$attrs.value }, set (value) { this.$emit('input', value) } }
    • 这是 IMO 的最佳解决方案。不需要事件总线!
    • 这太棒了。
    【解决方案4】:

    有很多方法可以做到这一点,例如 Vuex、事件总线、道具,您可以使用它们来管理模式是否必须打开或关闭。我将使用 .sync 修饰符向您展示我最喜欢的方式:

    首先我会简化你的问题(代码部分)

    父组件

    <template>
       <div>
         <button @click="dialog=true">Open Dialog</button>
         <Child :dialog.sync="dialog" />
       </div>
    </template>
    
    <script>
    import Child from './Child.vue'
    export default {
        components: {
          Child
        },
        data: {
          return {
            dialog: false
          }
       }
    }
    </script>
    

    子(对话框)组件

    <template>
      <v-layout row justify-center>
        <v-dialog v-model="dialog" persistent max-width="290">
          <v-card>
            <v-card-title class="headline">Use Google's location service?</v-card-title>
            <v-card-text>Let Google help apps determine location. This means sending anonymous location data to Google, even when no apps are running.</v-card-text>
            <v-card-actions>
              <v-spacer></v-spacer>
              <v-btn color="green darken-1" flat @click.native="close">Close</v-btn>
            </v-card-actions>
          </v-card>
        </v-dialog>
      </v-layout>
    </template>
    
    <script>
    
      export default {
        props: {
            dialog: {
            default: false
          }
        },
        methods: {
            close() {
            this.$emit('update:dialog', false)
          }
        }
      }
    
    </script>
    

    【讨论】:

    • 我找到了一个说明这一点的文档页面,但刚刚发现 medium.com/front-end-weekly/… 指出 .sync 已在 2.3 中重新添加 - 但它对我不起作用。
    • 这个解决方案对我有用,谢谢!出于某种原因,我无法掌握如何使用同步修饰符。
    • 如果不想在对话框上使用'persistent',也可以在对话框'@click:outside="close"'的点击外部事件上调用close方法跨度>
    【解决方案5】:

    我发现最简单的方法是:

    在组件的data()中,返回一个属性,比如说对话框。

    当你包含一个组件时,你可以设置一个对你的组件标签的引用。例如:

    import Edit from '../payment/edit.vue';
    
    <edit ref="edit_reference"></edit>
    

    然后,在我的组件内部,我设置了一个方法:

            open: function () {
                var vm = this;
    
                vm.dialog = true;
            }
    

    最后,我可以从父级调用它,使用:

      editar(item)
      {
          var vm = this;
    
          vm.$refs.edit_reference.open();
      }
    

    【讨论】:

    • 谢谢。为什么要写“var vm = this”而不是较短的“this.$refs.edit_reference.open()”?
    • 因为范围问题,虽然我不太确定这会不会是个问题,只是为了安全起见,因为我正在学习组件的概念。
    • 这太棒了!
    【解决方案6】:

    简单的最小工作示例

    codepen

    value 属性作为value 传递给v-dialog 组件,并在您想要关闭它时从子对话框发出input 事件:

    //CustomDialog.vue
    <v-dialog :value="value" @input="$emit('input', $event)">
      <v-btn color="red" @click.native="$emit('input', false)">Close</v-btn>
    </v-dialog>
    ...
    props:['value']
    

    并将 v-model 添加到您的父级

    //Parent.vue
    <custom-dialog v-model="dialog">
    

    所以没有自定义事件总线,没有data,没有watch,没有computed

    【讨论】:

    • 这似乎是一个非常简单的解决方案,但我很难理解为什么我们应该监听输入事件并在声明中重新触发输入事件。我找不到任何文档在何时何地触发此输入事件...谢谢
    • @LeonardoBernardini 因为当v-dialog "value" 更改时会发出 "input" 事件。例如,如果我们不使用“persistent”属性,那么在 v-dialog 之外单击也会触发该事件。所以我们也用它来覆盖这种情况。而且v-model 也不应该用在道具上,在这种情况下是value
    • @LeonardoBernardini 请注意,我在过去的另一个问题中也解释过,所以请查看整个线程:stackoverflow.com/questions/49310417/…(我不确定 Vue 从那时起是否改变了一些做法)跨度>
    • 谢谢,这似乎没有记录,而且令人困惑......现在很清楚了!
    • 只有在不检查道具类型时才有效。尝试添加 props:{value: { type: Boolean, required: true }}。它会显示 Invalid prop: type check failed for prop 'value'。预期的布尔值,当您按 ESC 键关闭对话框时未定义。 codepen.io/duongthienlee/pen/abNjrbv?editors=1011
    【解决方案7】:

    我更喜欢用这个:

    DialogConfirm.vue

    <template>
      <v-dialog :value="visible" max-width="450">
        <v-card>
          <v-card-title v-text="title" />
          <v-card-text v-text="message" />
          <v-card-actions v-if="visible">
            <template v-for="action in value">
              <v-spacer :key="action.label" v-if="typeof action == 'string'" />
              <v-btn
                v-else
                text
                :key="action.label"
                v-text="action.label"
                @click="doAction(action.action)"
                :color="action.color"
              />
            </template>
          </v-card-actions>
        </v-card>
      </v-dialog>
    </template>
    <script lang="ts">
    import Vue from 'vue'
    import Component from 'vue-class-component';
    import { Prop, Watch } from 'vue-property-decorator';
    
    @Component
    export default class DialogConfirm extends Vue {
    
      @Prop({ type: String, default: "Confirm" })
      title: string
    
      @Prop({ type: String, default: "Are you sure?" })
      message: string
    
      @Prop({ type: Array, default: undefined })
      value: { label: string, action: () => boolean, color: string }[]
    
      get visible() {
        return Array.isArray(this.value) && this.value.length > 0
      }
    
      doAction(action: () => boolean) {
        if ('undefined' == typeof action || action() !== false) {
          this.$emit('input', null)
        }
      }
    }
    </script>
    

    使用示例

    /** Disable AP Mode */
      setApMode(enable: boolean) {
        const action = () => {
          Api.get('wifi', {
            params: {
              ap: enable
            }
          }).then(response => this.$store.dispatch('status'))
        }
        if (enable == true) {
          // No confirmation
          return action();
        }
        this.dialogTitle = 'Are you sure?'
        this.dialogMessage = "you may lost connection to this device.";
        this.dialogActions = [
          {
            label: 'Cancel',
            color: 'success'
          },
          'spacer',
          {
            label: "OK, Disable it",
            color: "error",
            action
          }
        ]
      }
    
    

    【讨论】:

      【解决方案8】:
      methods: {
        openDialog(e) {
          this.dialog = true;
        }
      },
      

      这个适合我

      【讨论】:

        猜你喜欢
        • 2019-07-24
        • 2021-09-22
        • 2018-07-15
        • 2023-01-18
        • 2018-12-30
        • 2021-01-30
        • 1970-01-01
        • 2022-01-23
        • 1970-01-01
        相关资源
        最近更新 更多