Nemozar
@Nemozar
Php developer

Как передавать данные между двумя компонентами в Vue?

Добрый день.

Разбираюсь с Vue и хочу сделать свой компонент который позволит сделать универсальный механизм отображения форм и редактирование/добавление значений в таблице (и любых других форм). Кратко компонент выглядит так:

template.vue
<pages :currentPage.sync="currentPage" >
        <page name="index" title='Список проектов'>
            <template slot="toolbar">
                <v-btn color="primary" dark class="mb-2" @click="newProject">Новый проект</v-btn>
            </template>
            <v-data-table
                :headers="headers"
                :items="projects"
                hide-actions
                class="elevation-1"
            >
                ....
            </v-data-table>
        </page>
        <page parent="index" name="edit" title='Редактирование'>
....
</page>
</pages>


pages.vue
<template>
    <v-card width="100%" class="flexcard">
        <v-card-title primary-title>
            <v-breadcrumbs divider="/" class="pa-0">
                ....
            </v-breadcrumbs>
            <v-spacer></v-spacer>
            <v-card ref="toolbar">
               ......тут надо вставить slot toolbar из дочернего компонента page
            </v-card>
        </v-card-title>
        <v-card-text class="grow">
            <slot></slot>
        </v-card-text>
        <v-card-actions>
            <v-spacer></v-spacer>
        </v-card-actions>
    </v-card>
</template>


page.vue
<template>
    <div>
        <div v-if="name==pagesActive">
            <slot name="toolbar"></slot>
            <slot></slot>
        </div>
    </div>
</template>

<script>
    export default {
        data: () => ({

        }),
        computed:{
            pagesActive () {
                return this.$parent.$parent.activePage
            }
        },

        created () {
..вот тут хотелось бы получить доступ к родительскому компоненту и его слоту
            // this.$parent.$refs.toolbar = '123123'
        }
    }
</script>


кучу статей уже прочитал что передача от родителя к дочернему элементу очень затруднительно. При этом мне надо передать не просто значения а желательно весь slot='toolbar' из page.vue в родительский элемент в pages.vue.

Да я могу добраться до этого элемента из page.vue this.$parent.$children.find .... но больше вопрос как правильно. Можно ли организовать мою логику или это излишество и все надо постоянно копировать от страницы к странице.

Что я хочу в итоге получить:
Универсальный компонент, который позволит на одном роуте строить несколько страниц. Т.е. будет несколько страниц: список проектов, редактирование/добавление проекта, редактирование дополнительных параметров проекта. Хочу чтобы это все было сделано красиво через хлебные крошки и т.п. (сейчас в принципе уже все реализовано) но хочу воспользоваться пространством родительского компонента pages.vue чтобы вставить туда элементы управления для определенной page.

Зачем мне это. хочу себя ограничить от написания типового кода разметка страницы, заголовок, хлебные крошки, элементы управления в шапке и в подвале формы, единые переменные (т.к. добавление/редактирование и отображение таблицы это по сути одна форма, но хочу сделать ее красиво)
  • Вопрос задан
  • 1024 просмотра
Пригласить эксперта
Ответы на вопрос 1
Nemozar
@Nemozar Автор вопроса
Php developer
Если кому интересно, реализовал данную задачу. Весь исполняемый код и шаблон пришлось перенести в компонент Page.vue

Вот листинг кода:
Pages.vue
<template>
    <div style="width: 100%; height: 100%">
        <slot></slot>
    </div>
</template>

<style>
    .flexcard {
        display: flex;
        flex-direction: column;
    }
</style>

<script>
    export default {
        data: () => ({
            pages : []
        }),
        mounted () {
            this.initPages()
        },
        methods: {
            initPages() {
                this.$children.forEach((child) => {
                    if (child.$options.name == 'Page'){
                        this.pages.push(child)
                    }
                })
            },
        }
    }
</script>


Page.vue
<template>
    <v-card width="100%" class="flexcard" v-if="name==activePage" style="height: 100%">
        <v-card-title primary-title style="padding-bottom: 0; padding-top: 0;">
            <slot name="breadcrumbs">
                <v-breadcrumbs divider="/" class="">
                    <v-breadcrumbs-item
                        v-for="item in breadcrumbs"
                        :key="item.text"
                        :disabled="item.disabled"
                        @click.native="activePage=item.page"
                        class="pa-0"
                    >
                        {{ item.text }}
                    </v-breadcrumbs-item>
                </v-breadcrumbs>
            </slot>
            <v-spacer></v-spacer>
            <slot name="toolbar"></slot>
        </v-card-title>
        <v-card-text class="grow">
            <slot></slot>
        </v-card-text>
        <v-card-actions>
            <v-spacer></v-spacer>
            <slot name="actions"></slot>
        </v-card-actions>
    </v-card>
</template>

<script>
    export default {
        data: () => ({

        }),
        computed:{
            name(){
                return this.$attrs.name
            },
            activePage : {
                get: function () {
                    return this.$store.state.activePage
                },
                set: function (newValue) {
                    this.$store.dispatch('setActivePage', newValue)
                }
            },
            breadcrumbs(){
                if (this.activePage) {
                    console.log(this.activePage)
                    let breadcr = this.getBreadcrumb(this.activePage).reverse()
                    return breadcr
                }else{
                    return []
                }
            },
        },
        methods:{
            getBreadcrumb(pageName) {
                let items = []
                let page = this.getPageByName(pageName)
                if (page) {
                    items.push({
                        text: page.$attrs.title,
                        page: page.$attrs.name
                    })
                    if (page.$attrs.parent) {
                        let parentItem = this.getBreadcrumb(page.$attrs.parent)
                        items = items.concat(parentItem)
                    }
                }
                return items
            },
            getPageByName(name){
                if (this.$parent.pages && this.$parent.pages.length > 0){
                    let page = this.$parent.pages.find((p) => {
                        return p.$attrs.name == name
                    })
                    return page
                }
                return null
            },
        },
    }
</script>


В любом файле используем конструкцию
<template>
    <pages style="height : 100%">
        <vue-element-loading :active="loading" spinner="bar-fade-scale"/>
        <page name="index" title='Список проектов'>
            <template slot="toolbar">
                <v-btn color="primary" small dark class="mb-2" @click.native="newProject">Новый проект</v-btn>
            </template>
            <v-data-table
                :headers="headers"
                :items="projects"
                hide-actions
                class="elevation-1"
            >
                <template slot="items" slot-scope="props" :disabled="true">
                    <td>{{ props.item.name }}</td>
                    <td class="justify-center layout px-0">
                        <v-btn flat icon small  @click="editItem(props.item)">
                            <v-icon
                                small

                            >
                                edit
                            </v-icon>
                        </v-btn>

                        <v-btn flat icon small color="error"
                               @click="deleteItem(props.item)"
                               :loading="props.item.loading"
                        >
                            <v-icon small>
                                delete
                            </v-icon>
                        </v-btn>
                    </td>
                </template>
            </v-data-table>
        </page>
        <page parent="index" name="edit" title='Редактирование'>
            <v-form v-model="valid">
                <v-layout wrap>
                    <v-flex xs12>
                        <v-text-field name="name" :error-messages="errors.collect('name')" :rules="nameRules" required v-model="editedItem.name" label="Название"></v-text-field>
                    </v-flex>
                    <v-flex xs12>
                        <v-spacer></v-spacer>
                    </v-flex>
                </v-layout>
            </v-form>
            <template slot="actions">
                <v-btn small @click.native="close">Отмена</v-btn>
                <v-btn
                    small
                    :disabled="!valid"
                    :loading="waitSave"
                    @click.native="save"
                >
                    Сохранить
                </v-btn>
            </template>
        </page>
    </pages>
</template>


Можно осуществить хлебные крошки любого уровня за счет цепочки parent ссылок в компоненте page
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы