【问题标题】:How do I open and close v-dialog from a component under its parent? Use Vuex?如何从其父组件下的组件打开和关闭 v-dialog?使用 Vuex?
【发布时间】:2020-09-03 10:51:23
【问题描述】:

我需要从数据表组件打开一个 CRUD 对话框。对话框和数据表共享同一个父级。数据表是可重用的,但 CRUD 对话框不是。

用例似乎很常见。管理页面包含一个数据表,每行包含一个打开编辑对话框的编辑按钮。

我尝试在下面使用 Vuex - 但是出现了这个错误:

[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'showUserModal' of undefined"

found in

---> <VBtn>
       <VSimpleTable>
         <VData>
           <VDataTable>
             <DataTable> at src/components/DataTable.vue
               <Settings> at src/views/Settings.vue
                 <VContent>
                   <VApp>
                     <App> at src/App.vue
                       <Root>

为什么导入的 mutator 不可用,这是实现通用功能的好方法吗?

我使用这两种方法得出了我当前的解决方案 https://markus.oberlehner.net/blog/building-a-modal-dialog-with-vue-and-vuex/ https://forum.vuejs.org/t/how-to-trigger-a-modal-component-from-vuex-store/27243/9

UserAdmin.vue

<template>
  <v-container fluid >
      <DataTable v-bind:rows="allUsers" v-bind:headers="headers" />
      <EditUser />
  </v-container>
</template>

<script>
import { mapGetters, mapActions } from "vuex";
import DataTable from '../components/DataTable';
import EditUser from '../modals/EditUser';

export default {
  name: 'UserAdmin',

  methods: {
    ...mapActions(["getUsers"])
  },

  computed: mapGetters(["allUsers"]),

  components: {
    DataTable, EditUser
  },

  data(){
    return {
      headers: [ 
        { text: 'Name', value: 'name' },
        { text: 'Username', value: 'email' },
        { text: 'Administrator', value: 'admin' },
        { text: "", value: "controls", sortable: false}
      ]
    }
  },

  created(){
    this.getUsers();
  }
}
</script>

DataTable.vue

<template>
    <v-data-table
        :headers="headers"
        :items="rows"
        :items-per-page="5"
        class="elevation-1"
    >
    <!-- https://stackoverflow.com/questions/59081299/vuetify-insert-action-button-in-data-table-and-get-row-data --> 
     <template v-slot:item.controls="props">
        <v-btn class="my-2" fab dark x-small color="blue" @click="onButtonClick(props.item.email)">
          <v-icon dark>mdi-pencil</v-icon>
        </v-btn>
      </template> 
    </v-data-table>
</template>

<script>

  import { mapMutations } from "vuex";

  export default {
    name: "DataTable",
    props:["headers", "rows"],
    methods: {
      ...mapMutations(["toggleUserModal"]),
      onButtonClick: function(email) {
        console.log("clicked: " + email)
        this.toggleUserModal();
      }
    }
  }
</script>

EditUser.vue

<template>
  <v-row justify="center">
    <v-dialog v-model="dialog" persistent max-width="600px" v-show='showUserModal'>
      <v-card>
        <v-card-title>
          <span class="headline">User Profile</span>
        </v-card-title>
        <v-card-text>
          <v-container>
            <v-row>
              <v-col cols="12" sm="6" md="4">
                <v-text-field label="Legal first name*" required></v-text-field>
              </v-col>
              <v-col cols="12" sm="6" md="4">
                <v-text-field label="Legal middle name" hint="example of helper text only on focus"></v-text-field>
              </v-col>
              <v-col cols="12" sm="6" md="4">
                <v-text-field
                  label="Legal last name*"
                  hint="example of persistent helper text"
                  persistent-hint
                  required
                ></v-text-field>
              </v-col>
              <v-col cols="12">
                <v-text-field label="Email*" required></v-text-field>
              </v-col>
              <v-col cols="12">
                <v-text-field label="Password*" type="password" required></v-text-field>
              </v-col>
              <v-col cols="12" sm="6">
                <v-select
                  :items="['0-17', '18-29', '30-54', '54+']"
                  label="Age*"
                  required
                ></v-select>
              </v-col>
              <v-col cols="12" sm="6">
                <v-autocomplete
                  :items="['Skiing', 'Ice hockey', 'Soccer', 'Basketball', 'Hockey', 'Reading', 'Writing', 'Coding', 'Basejump']"
                  label="Interests"
                  multiple
                ></v-autocomplete>
              </v-col>
            </v-row>
          </v-container>
          <small>*indicates required field</small>
        </v-card-text>
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn color="blue darken-1" text @click="dialog = false">Close</v-btn>
          <v-btn color="blue darken-1" text @click="dialog = false">Save</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </v-row>
</template>

<script>
  export default {
    data: () => ({
        dialog: false,
    }),
    computed: {
        showUserModal(){
            return this.$store.state.showUserModal
        }
    }
  }
</script>

modals.js

const state = {
    showUserModal: false
}

const mutations = {
    toggleUserModal: () => (this.showUserModal = !this.showUserModal)
}

const getters = {
    showUserModal: state => {
        return state.showUserModal
    }
}

export default {
    state,
    getters,
    mutations
}

基于@Anatoly 建议的新代码 - 除了从对话框发出的事件外,一切正常,例如:onEditUserConfirmed 不会在父组件中拾取。

模态组件

<template>
  <v-row justify="center">
    <v-dialog v-model="visible" persistent max-width="600px">
      <v-card v-if="user">
        <v-card-title>
          <span class="headline">User Profile</span>
        </v-card-title>
        <v-card-text>
          <v-container>
            <v-row>
              <v-col cols="12" sm="6" md="4">
                <v-text-field v-model="user.name" label="Legal first name*" required></v-text-field>
              </v-col>
              <v-col cols="12" sm="6" md="4">
                <v-text-field label="Legal middle name" hint="example of helper text only on focus"></v-text-field>
              </v-col>
              <v-col cols="12" sm="6" md="4">
                <v-text-field
                  label="Legal last name*"
                  hint="example of persistent helper text"
                  persistent-hint
                  required
                ></v-text-field>
              </v-col>
              <v-col cols="12">
                <v-text-field label="Email*" required></v-text-field>
              </v-col>
              <v-col cols="12">
                <v-text-field label="Password*" type="password" required></v-text-field>
              </v-col>
              <v-col cols="12" sm="6">
                <v-select :items="['0-17', '18-29', '30-54', '54+']" label="Age*" required></v-select>
              </v-col>
              <v-col cols="12" sm="6">
                <v-autocomplete
                  :items="['Skiing', 'Ice hockey', 'Soccer', 'Basketball', 'Hockey', 'Reading', 'Writing', 'Coding', 'Basejump']"
                  label="Interests"
                  multiple
                ></v-autocomplete>
              </v-col>
            </v-row>
          </v-container>
          <small>*indicates required field</small>
        </v-card-text>
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn color="blue darken-1" text @click="onCancel">Close</v-btn>
          <v-btn color="blue darken-1" text @click="onSave">Save</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </v-row>
</template>

<script>
export default {
  name: "EditUser",
  props: {
    user: Object,
    visible: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    onSave() {
      console.log('save button gets here...')
      this.$emit("onEditUserConfirmed", this.user);
    },
    onCancel() {
      console.log('cancel button gets here...')
      this.$emit("onEditUserCancelled");
    }
  }
};
</script>

父组件

<template>
  <v-container fluid>
    <v-data-table :headers="headers" :items="allUsers" :items-per-page="5" class="elevation-1">
      <!-- https://stackoverflow.com/questions/59081299/vuetify-insert-action-button-in-data-table-and-get-row-data -->
      <template v-slot:item.controls="props">
        <v-btn class="my-2" fab dark x-small color="blue" @click="onEditClick(props.item)">
          <v-icon dark>mdi-pencil</v-icon>
        </v-btn>
      </template>
    </v-data-table>

    <EditUser
      :user="user"
      :visible="isDialogVisible"
      @confirmed="onEditUserConfirmed"
      @cancelled="onEditUserCancelled"
    />
  </v-container>
</template>

<script>
import { mapGetters, mapActions } from "vuex";
import EditUser from "../modals/EditUser";

export default {
  name: "Settings",
  data() {
    return {
      user: null,
      isDialogVisible: false,
      headers: [
        { text: "Name", value: "name" },
        { text: "Username", value: "email" },
        { text: "Administrator", value: "admin" },
        { text: "", value: "controls", sortable: false }
      ]
    };
  },
  methods: {
    ...mapActions(["getUsers"]),
    onEditClick: function(user) {
      console.log('Editing user: ' + user.email)
      this.user = user;
      this.isDialogVisible = true;
    },
    onEditUserConfirmed(user) {
      console.log('Saving user: ' + user.email)
      this.isDialogVisible = false;
    },
    onEditUserCancelled () {
      this.isDialogVisible = false;
    }
  },

  computed: mapGetters(["allUsers"]),

  components: {
    EditUser
  },

  created() {
    this.getUsers();
  }
};
</script>

【问题讨论】:

    标签: vue.js vuex vuetify.js


    【解决方案1】:

    我不建议在此任务中使用状态。因为它不是一个非常复杂的场景。你应该使用道具和事件来处理这种情况

    稍微修改一下代码。

    DataTable.vue

    <script>
        methods: {
          onButtonClick: function(email) {
            console.log("clicked: " + email)
            this.$emit('openDialog') // or use any name here
          }
        }
    </script>
    

    UserAdmin.vue

    <template>
      <v-container fluid >
          <!-- Listen to the event that you are emitting from DataTable.vue  -->
          <DataTable :rows="allUsers" :headers="headers" @showDialog="editUser = true" />
    
          <!-- Pass that variable as a Prop -->
          <EditUser :showDialog="editUser"  />
      </v-container>
    </template>
    
    <script>
    ....
        data: () => ({
          headers: [ 
            { text: 'Name', value: 'name' },
            { text: 'Username', value: 'email' },
            { text: 'Administrator', value: 'admin' },
            { text: "", value: "controls", sortable: false}
          ],
    
          editUser: false, // a flag to keep the status of modal.
        })
    
    ....
    </script>
    

    EditUser.vue

    <script>
      export default {
        props: {
           showDialog: {
               type: Boolean,
               default: false
           }
        },
        data: () => ({
            dialog: false,
        }),
        mounted() {
            this.dialog = this.showDialog
        },
        watch: {
            showDialog() {
                if (this.showDialog)
                    this.dialog = true
            }
        }
      }
    </script>
    

    我希望它应该起作用,它在我的场景中对我有用。我不建议在这种简单的单级结构中使用 Vuex 商店。 Vuex应该用于一些复杂的数据结构,其中有很深的组件。

    代码可能有一些语法错误(请告诉我)。但我希望我只是传达了这个概念

    【讨论】:

      【解决方案2】:
      1. 使用表格组件中的事件通知父组件您希望编辑用户(在此事件中发送选定的用户)。
      2. 在父组件中捕获事件,将事件中的用户写入 data 部分中的道具,并将此道具传递给对话框组件。
      3. 使用道具显示/隐藏父组件的对话框
      4. 在对话确认后使用事件接收编辑的用户。

      类似这样的:

      父组件

      <DataTable v-bind:rows="allUsers" v-bind:headers="headers" @onEdit="onEditUser"/>
      <EditUser :user="user" :visible="isDialogVisible" @confirmed="onEditUserConfirmed" @cancelled="onEditUserCancelled"/>
      
      ...
      data: {
        return {
          // other data
          user: null,
          isDialogVisible : false
        }
      },
      methods: {
        onEditUser (user) {
          this.user = user
          this.isDialogVisible = true
        },
        onEditUserConfirmed (user) {
         // hide a dialog 
         this.isDialogVisible = false 
         // save a user and refresh a table
        },
        onEditUserCancelled () {
         // hide a dialog 
         this.isDialogVisible = false 
        }
      }
      

      表格组件:

      // better send a whole user object insteaf of just e-mail prop? It's up to you
      @click="onButtonClick(props.item)"
      ...
      methods: {
            onButtonClick: function(user) {
              this.$emit('onEdit', user)
            }
          }
      

      对话框组件:

       <v-dialog v-model="visible" ...
         // render card only if user is passed
         <v-card v-if="user">
         <v-col cols="12" sm="6" md="4">
           <v-text-field v-model="user.firstName" label="Legal first name*" required></v-text-field>
          </v-col>
      ...
      <v-btn color="blue darken-1" text @click="onCancel">Close</v-btn>
      <v-btn color="blue darken-1" text @click="onSave">Save</v-btn>
      ...
      export default {
        props: {
         user: {
           type: Object
         },
         visible: {
           type: Boolean,
           default: false
         }
        },
      ...
        methods: {
          onSave() {
            this.$emit('confirmed', this.user)
          },
          onCancel () {
            this.$emit('cancelled')
          }
        }
      }
      
      

      【讨论】:

      • 这行得通,但我也将数据表组件向下折叠到父级中,并将上述内容应用于 v-data-table。似乎这会减少开销。
      • 我发现发出的动作没有在父级中拾取。但除此之外,一切正常。
      • 我固定在我的回答中发出呼叫。检查它们。
      • 那行得通。父级中@v:on 之间是否有任何偏好?
      • @ 只是v-on 的简写,:v-bind 的简写。我总是使用@:,这样模板中的noise 就更少了,而真正的内容更多了。
      猜你喜欢
      • 1970-01-01
      • 2020-05-11
      • 2021-12-02
      • 2019-01-13
      • 2019-09-30
      • 2021-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多