source

상위 구성 요소에서 v-dialog를 열고 닫으려면 어떻게 해야 합니까?Vuex를 사용하시겠습니까?

gigabyte 2022. 8. 15. 21:23
반응형

상위 구성 요소에서 v-dialog를 열고 닫으려면 어떻게 해야 합니까?Vuex를 사용하시겠습니까?

데이터 테이블 구성 요소에서 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>

Import된 뮤테이터를 사용할 수 없는 이유는 무엇입니까?또, 이것이, 공통의 기능을 실현하기 위한 좋은 어프로치입니까?

이 2가지 접근방식을 사용하여 현재의 솔루션에 도달했습니다.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

사용자 관리자표시하다

<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>

데이터 테이블표시하다

<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>

Edit User(사용자 편집)표시하다

<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>

모듈.개요

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>

이 태스크에 스테이트를 사용하는 것은 추천하지 않습니다.그렇게 복잡한 시나리오가 아니기 때문입니다.이런 상황에 대처하기 위해서 소품이나 이벤트를 이용해야 합니다.

코드를 조금만 수정해 주세요.

데이터 테이블표시하다

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

사용자 관리자표시하다

<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>

Edit User(사용자 편집)표시하다

<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를 사용해야 합니다.

코드에 구문 오류가 있을 수 있습니다(알려주세요).그냥 콘셉트만 전달했으면 좋겠어요.

  1. 테이블 컴포넌트의 이벤트를 사용하여 사용자를 편집하는 부모 컴포넌트에 통지합니다(이 이벤트에서는 선택한 사용자를 송신합니다).
  2. 상위 구성 요소에서 이벤트를 포착하고, 이벤트의 사용자를 데이터 섹션의 소품에 쓴 다음 이 소품을 대화 상자 구성 요소에 전달합니다.
  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')
    }
  }
}

언급URL : https://stackoverflow.com/questions/61856054/how-do-i-open-and-close-v-dialog-from-a-component-under-its-parent-use-vuex

반응형