如何从其父组件下的组件打开和关闭 v-dialog?使用 Vuex?
Posted
技术标签:
【中文标题】如何从其父组件下的组件打开和关闭 v-dialog?使用 Vuex?【英文标题】:How do I open and close v-dialog from a component under its parent? Use 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://***.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- 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->
<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://***.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>
【问题讨论】:
【参考方案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】:-
使用表格组件中的事件通知父组件您希望编辑用户(在此事件中发送选定的用户)。
在父组件中捕获事件,将事件中的用户写入 data 部分中的道具,并将此道具传递给对话框组件。
使用道具显示/隐藏父组件的对话框
在对话确认后使用事件接收编辑的用户。
类似这样的:
父组件
<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
就更少了,而真正的内容更多了。以上是关于如何从其父组件下的组件打开和关闭 v-dialog?使用 Vuex?的主要内容,如果未能解决你的问题,请参考以下文章
使用“动态宽度”时如何将过渡应用于 v-dialog,这意味着宽度 =“未设置”?
如何从 angular-material2 对话框与其父级进行通信