基于子组件构建列表组件并实现视图模式切换功能


上篇教程学院君给大家演示了如何将文章发布表单组件拆分成多个子组件,然后基于这些子组件构建登录表单,今天我们来看看如何基于子组件构建文章列表组件。

我们将基于 Bootstrap 提供的列表组件样式代码进行演示,所以只需要关注 Vue 和 JavaScript 代码即可。

编写列表骨架组件

resources/js/components 目录下新建 list 子目录来存放列表骨架组件和列表项子组件,然后在该目录下新建一个从 Bootstrap 的列表组件抽象出来的、一个可以被所有 Vue 列表组件共用的骨架组件 ListSection.vue

<style scoped>
.card-header h5 {
    margin-top: 0.5em;
    display: inline-block;
}
.card-header .view-mode {
    float: right;
}
</style>

<template>
<div class="card">
    <div class="card-header">
        <h5><slot name="title"></slot></h5>
        <button class="btn btn-success view-mode" @click.prevent="switch_view_mode">
            {{ view.switch_to }}
        </button>
    </div>
    <div class="card-body">
        <ul class="list-group" v-if="view.mode === 'list'">
            <slot></slot>
        </ul>
        <div class="row row-cols-1 row-cols-md-3" v-else>
            <slot></slot>
        </div>
    </div>
</div>
</template>

<script>
export default {
    props: ['view_mode'],
    data() {
        return {
            view: {
                'mode': this.view_mode,
                'switch_to': this.view_mode === 'list' ? '卡片视图' : '列表视图'
            }
        }
    },
    methods: {
        switch_view_mode() {
            if (this.view.mode === 'list') {
                this.view.mode = 'card';
                this.view.switch_to = '列表视图';
            } else {
                this.view.mode = 'list';
                this.view.switch_to = '卡片视图';
            }
            this.$emit('view-mode-changed', this.view.mode)
        }
    }
}
</script>

除了基本的 HTML 模板代码之外,我们还在这个骨架组件中实现了列表视图模式切换功能,比如列表视图(view.mode 值为 list)、卡片视图(view.mode 值为 card),默认展示什么视图模式,由父级作用域中编写的具体列表组件决定,然后通过 props 属性将对应的视图模式传递到骨架组件即可。

你可以通过点击列表组件标题右侧的切换按钮来切换列表视图模式,这个点击事件会调用 switch_view_mode 方法,该方法通过对相应的模型属性进行设置以便结合 Vue 组件的数据绑定机制来完成视图模式的切换,此外,该方法还会通过 $emit 触发父级作用域中定义的 view-mode-changed 事件函数,从而将切换后的视图模式传递给父级作用域。关于该事件函数的定义我们马上会在引入 ListSection 的文章列表组件中看到。

对于列表标题、不同视图模式对应的列表项渲染这些与具体的列表组件相关的东西,都通过插槽交由调用该骨架组件的父级作用域对应的列表组件去决定和定制。

列表视图对应列表项子组件

我们先在 list 子目录下创建列表视图对应的列表项子组件 ListItem.vue

<template>
    <li class="list-group-item">
        <a :href="url">
            <slot></slot>
        </a>
    </li>
</template>

<script>
export default {
    props: ['url']
}
</script>

整体代码非常简单,只是渲染一个 li 元素而言,相关动态属性和文本从父级作用域通过 props 和插槽传递过来。

卡片视图对应列表项子组件

然后在同级目录下新建卡片视图对应的列表项子组件 CardItem.vue

<template>
  <div class="col mb-4">
        <div class="card">
            <div class="card-body">
                <h5 class="card-title"><slot></slot></h5>
                <a :href="url" class="btn btn-primary">点击查看</a>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    props: ['url']
}
</script>

和上面的列表项子组件一样,非常简单,不多做介绍了。

基于子组件构建文章列表组件

接下来,我们就可以组合上面的列表骨架组件和列表项子组件构建文章列表了,在 resources/js/components 目录下新建 PostList.vue,编写组件代码如下:

<style scoped>
.post-list {
    margin-top: 50px;
}
</style>

<template>
    <div class="post-list">
        <ListSection :view_mode="view_mode" @view-mode-changed="change_view_mode">
            <template #title>文章列表</template>
            <template v-if="view_mode === 'list'">
                <ListItem v-for="post in posts" :key="post.id" :url="post.url">
                    {{ post.title }}
                </ListItem>
            </template>
            <template v-else>
                <CardItem  v-for="post in posts" :key="post.id" :url="post.url">
                    {{ post.title }}
                </CardItem>
            </template>
        </ListSection>
    </div>
</template>

<script>
import ListSection from "./list/ListSection";
import ListItem from "./list/ListItem";
import CardItem from "./list/CardItem";

export default {
    components: {CardItem, ListItem, ListSection},
    data() {
        return {
            posts: [],
            view_mode: 'list'
        }
    },
    mounted() {
        axios.get('/get_posts').then(resp => {
            this.posts = resp.data;
        }).catch(error => {
            console.log('从服务端加载文章数据失败');
        });
    },
    methods: {
        change_view_mode(mode) {
            this.view_mode = mode;
        }
    }
}
</script>

PostList 组件中,我们通过引入 ListSectionListItemCardItem 来使用它们,并填充相应的 props 属性和插槽内容,默认的视图模式是 list,即列表视图。

在这个父级作用域中,我们基于列表视图模式 view_mode 的值来判断使用列表视图列表项子视图循环渲染文章数据,还是使用卡片视图列表项子视图来循环渲染文章数据。如果 ListSection 子组件中的视图模式发生变更,会触发这里定义的 view-mode-changed 事件,并执行 change_view_mode 方法修改本组件中的 view_mode 值,进而基于数据绑定切换不同样式子组件的渲染。

这里我们还定义了 mounted 钩子函数来初始化文章列表数据。关于 /get_posts 接口的后端路由实现,我们马上会介绍。

创建引入文章列表组件的视图

至此,我们就完成了 Vue 文章列表组件的开发,在 resources/js/app.js 中引入它:

Vue.component('post-list', require('./components/PostList.vue').default);

接着在 resources/views 下新建 posts.blade.php 来渲染文章列表组件:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>文章列表</title>

    <!-- Fonts -->
    <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">

    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body class="antialiased">
<div id="app" class="container">
    <post-list></post-list>
</div>
<script src="{{ asset('js/app.js') }}" type="text/javascript"></script>
</body>
</html>

注:任意引入 js/app.jscss/app.css 文件的 HTML 模板均可正常渲染 post-list 这个 Vue 组件,当然需要将其放置到 div[id=app] 容器中,因为 Vue 实例是挂载在它之上的。

注册后端路由

为了正常渲染这个 posts 视图,需要在 Laravel 后端注册如下两个路由:

Route::get('/posts', function () {
    return view('posts');
});
Route::get('/get_posts', function () {
    return [
        [
            'id' => 1,
            'url' => url('/post/1'),
            'title' => '测试文章1'
        ],
        [
            'id' => 2,
            'url' => url('/post/2'),
            'title' => '测试文章2'
        ],
        [
            'id' => 3,
            'url' => url('/post/3'),
            'title' => '测试文章3'
        ],
        [
            'id' => 4,
            'url' => url('/post/4'),
            'title' => '测试文章4'
        ],
        [
            'id' => 5,
            'url' => url('/post/5'),
            'title' => '测试文章5'
        ]
    ];
});

/get_posts 路由中,我们通过一个数组模拟文章数据并返回。

在浏览器中查看文章列表页面

如果你后台启动了 npm run watchphp artisan serve 此时就可以通过 http://localhost:3002/posts(配置了 BrowserSync,所以端口是 3002)访问文章列表页了:

-w1138

你可以通过点击右上角的绿色切换按钮在列表和卡片两个视图模式之间任意切换:

-w1136

好了,关于基于子组件构建文章列表组件功能我们就介绍到这里,下篇教程,学院君来给大家演示如何在 Vue 组件中使用过滤器对模型数据进行格式化。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: SOLID 原则在 Vue 组件开发中的应用:将单个表单组件拆分成可复用的子组件组合

>> 下一篇: 通过过滤器对 Vue 组件中的模型属性值进行格式化