Vue.js + Require.js 扩展 parent

Vue.js + Require.js extending parent

我是 Vue.js 的新手,但正在努力掌握它。到目前为止一切顺利,我已经走得很远了,但我仍然无法扩展 parents 模板。

我正在尝试制作扩展默认小部件布局的仪表板小部件(在 Boostrap 中)。请注意以下代码使用了 Vue、Require、Underscore 和 Axios。

Parent 文件 - _Global.vue

<template>
    <div class="panel panel-default">
        <div class="panel-heading">
            <b>{{ widgetTitle }}</b>
            <div class="pull-right">
                <a href="#" v-on:click="toggleMinimized"
                   v-bind:title="(isMinimized ? 'Show widget' : 'Hide widget')">
                    <i class="fa fa-fw" v-bind:class="isMinimized ? 'fa-plus' : 'fa-minus'"></i>
                </a>
            </div>
        </div>
        <div class="panel-body" v-if="!isMinimized">


            <div class="text-center text-muted" v-if="!isLoaded">
                <i class="fa fa-spin fa-circle-o-notch"></i><br />
            </div>

            <parent v-if="isLoaded">
                <!-- parent content should appear here when loaded -->
            </parent>
        </div>
    </div>
</template>
<script>
    export default {

        // setup our widget props
        props: {
            'minimized': {
                'default': false,
                'required': false,
                'type': Boolean
            }
        },

        // define our data
        data: function () {
            return {
                widgetTitle: 'Set widget title in data',
                isLoaded: false,
                isMinimized: this.$props.minimized
            }
        },

        // when vue is mounted, open our widget
        mounted: function () {
            if(!this.isMinimized) {
                this.opened();
            }
        },

        // define our methods
        methods: {

            // store our widget state to database
            storeWidgetState: function () {

                // set our data to send
                let data = {
                    'action' : 'toggleWidget',
                    'widget' : this.$options._componentTag,
                    'state' : !this.isMinimized
                };

                // post our data to our endpoint
                axios.post(axios.endpoint, data);
            },

            // toggle our minimized data
            toggleMinimized: function (e) {

                // prevent default
                e.preventDefault();

                // toggle our minimized state
                this.isMinimized = !this.isMinimized;

                // trigger opened if we aren't minimized
                if(!this.isMinimized) this.opened();

                // save our widget state to database
                this.storeWidgetState();
            },

            // triggered when opened from being minimized
            opened: function () {
                console.log('opened() method is where all widget logic should be placed');
            }
        }

    }
</script>

Child 文件 - Example.vue 应该使用 mixin 扩展 _Global.vue,然后在 .panel-body

中显示内容
<template>
    <div>
        I want this content to appear inside the .panel-body div

        {{ content }}

         <img v-bind:src="image.src" v-bind:alt="image.alt"
             v-if="image.src" class="img-responsive" style="margin: 0 auto" />
    </div>
</template>
<script>

    // import our widgets globals
    import Global from './_Global.vue'

    export default {

        components: {
            'parent': {
                // what can I possibly put here??
            }
        },

        // use our global mixin for all widgets
        mixins: [Global],

        // setup our methods for this widget
        methods: {

            opened: _.debounce(function () {

                // make sure this can only be opened once
                if(this.hasBeenOpened) return;
                this.hasBeenOpened = true;

                // temporarily allow axios to make external requests
                let axiosHeaders = axios.defaults.headers.common;
                let vm = this;
                axios.defaults.headers.common = {};

                axios.get('https://yesno.wtf/api')
                    .then(function (res) {

                        // set our content
                        vm.content = null;

                        // set our image content
                        vm.image.src = res.data.image;
                        vm.image.alt = res.data.answer;

                    })
                    .catch(function (err) {

                        // set our error text
                        vm.content = String(err);

                    })
                    .then(function () {

                        // this will always hit..
                        vm.isLoaded = true;

                    });

                // restore our axios headers for security
                axios.defaults.headers.common = axiosHeaders;
            }, 300)
        },

        // additional data
        data: function () {
            return {

                // set our widgets title
                widgetTitle: 'Test title',

                // logic for the specific widget
                hasBeenOpened: false,
                content: 'Loaded and ready to go...',
                image: {
                    src: false,
                    alt: null
                }
            };
        },

    }
</script>

目前我的 parent 模板完全覆盖了我的 child 视图。我能让它工作的唯一方法是在组件内显式定义模板参数 -> parent: {} 但我不想那样做...?

好的,感谢 Gerardo Rosciano 为我指明了正确的方向。我习惯于想出一个最终的解决方案。然后我们访问父方法和数据属性只是为了让一切正常工作。

Example.vue - 我们的示例小部件

<template>
    <div>
        <widget-wrapper>
            <span slot="header">Example widget</span>
            <div slot="content">
                <img v-bind:src="image.src" v-bind:alt="image.alt"
                     v-if="image.src" class="img-responsive" style="margin: 0 auto" />

                {{ content }}
            </div>
        </widget-wrapper>
    </div>
</template>
<script>

    // import our widgets globals
    import WidgetWrapper from './_Widget.vue'

    export default {

        // setup our components
        components: {
            'widget-wrapper': WidgetWrapper
        },

        // set our elements props
        props: {
            'minimized': {
                'type': Boolean,
                'default': false,
                'required': false
            }
        },

        // setup our methods for this widget
        methods: {

            loadContent: _.debounce(function () {

                // make sure this can only be opened once
                if(this.hasBeenOpened) return;
                this.hasBeenOpened = true;

                // temporarily allow axios to make external requests
                let axiosHeaders = axios.defaults.headers.common;
                let vm = this;
                axios.defaults.headers.common = {};

                axios.get('https://yesno.wtf/api')
                    .then(function (res) {

                        // set our content
                        vm.content = null;

                        // set our image content
                        vm.image.src = res.data.image;
                        vm.image.alt = res.data.answer;

                    })
                    .catch(function (err) {

                        // set our error text
                        vm.content = String(err);

                    })
                    .then(function () {

                        // this will always hit..
                        vm.isLoaded = true;

                    });

                // restore our axios headers for security
                axios.defaults.headers.common = axiosHeaders;
            }, 300)
        },

        // additional data
        data: function () {
            return {

                // global param for parent
                isLoaded: false,

                // logic for the specific widget
                hasBeenOpened: false,
                content: 'Loaded and ready to go...',
                image: {
                    src: false,
                    alt: null
                }
            };
        },

    }
</script>

_Widget.vue - 我们扩展的基本小部件

<template>
    <div class="panel panel-default">
        <div class="panel-heading">
            <b><slot name="header">Slot header title</slot></b>
            <div class="pull-right">
                <a href="#" v-on:click="toggleMinimized"
                   v-bind:title="(minimized ? 'Show widget' : 'Hide widget')">
                    <i class="fa fa-fw" v-bind:class="minimized ? 'fa-plus' : 'fa-minus'"></i>
                </a>
            </div>
        </div>
        <div class="panel-body" v-if="!minimized">
            <div class="text-center text-muted" v-if="!isLoaded">
                <i class="fa fa-spin fa-circle-o-notch"></i><br />
                Loading...
            </div>
            <div v-else>
                <slot name="content"></slot>
            </div>
        </div>
    </div>
</template>

<script>
    export default {

        // get loaded state from our parent
        computed: {
            isLoaded: function () {
                return this.$parent.isLoaded;
            }
        },

        // set our data element
        data: function () {
            return {
                minimized: false
            }
        },

        // when the widget is mounted, trigger open state
        mounted: function () {
            this.minimized = this.$parent.minimized;
            if(!this.minimized) this.opened();
        },

        // methods to manipulate our widget
        methods: {

            // save our widget state to database
            storeWidgetState: function () {

                // set our data to send
                let data = {
                    'action' : 'toggleWidget',
                    'widget' : this.$parent.$options._componentTag,
                    'state' : !this.minimized
                };

                // post this data to our endpoint
                axios.post(axios.endpoint, data);
            },

            // toggle our minimized state
            toggleMinimized: function (e) {

                // prevent default
                e.preventDefault();

                // toggle our minimized state
                this.minimized = !this.minimized;

                // trigger opened if we aren't minimized
                if(!this.minimized) this.opened();

                // save our widget state to database
                this.storeWidgetState();
            },

            // when widget is opened, load content
            opened: function () {

                // make sure we have a valid loadContent method
                if(typeof this.$parent.loadContent === "function") {
                    this.$parent.loadContent();
                } else {
                    console.log('You need to define a loadContent() method on the widget');
                }
            }
        }
    }
</script>