Django-Vue搭建个人博客:翻页与监听

7546 views, 2023/10/16 updated   Go to Comments

上一章的详情页面跳转,用到了 vue-router 动态匹配路由的能力。而翻页功能通常不会直接改变当前路由,而是修改 url 中的查询参数来实现。它两的区别如下:

# 改变路由
https://abc.com/2
# 改变查询参数
http://abc.com/?page=2

这一点微妙的区别就导致翻页的实现方式和详情页跳转不太一样。

本章实现的是文章列表的翻页,所有内容均在 ArticleList.vue 中完成。

模板

模板改动如下:

<!--  frontend/src/components/ArticleList.vue  -->


<template>
    ...

    <div id="paginator">
        <span v-if="is_page_exists('previous')">
            <router-link :to="{ name: 'Home', query: { page: get_page_param('previous') } }">
                Prev
            </router-link>
        </span>
        <span class="current-page">
            {{ get_page_param('current') }}
        </span>
        <span v-if="is_page_exists('next')">
            <router-link :to="{ name: 'Home', query: { page: get_page_param('next') } }">
                Next
            </router-link>
        </span>
    </div>

</template>

...

这里面实际上只用到了两个新的方法(马上要写):

  • is_page_exists(...) 用于确认需要跳转的页面是否存在,如果不存在那就不渲染对应的跳转标签。它的唯一参数用于确定页面的方向(当前页、上一页或下一页?)。
  • get_page_param(...) 用于获取页码,它的参数作用也类似,基本上就是个标记。

router-link 通过 query 传递参数,看来还是挺方便的。

脚本

脚本部分是本章的核心内容,看仔细了:

<!--  frontend/src/components/ArticleList.vue  -->

...

<script>
    import axios from 'axios';

    export default {
        name: ...,
        data: ...,
        mounted() {
            this.get_article_data()
        },
        methods: {
            formatted_time: ...,
            // 判断页面是否存在
            is_page_exists(direction) {
                if (direction === 'next') {
                    return this.info.next !== null
                }
                return this.info.previous !== null
            },
            // 获取页码
            get_page_param: function (direction) {
                try {
                    let url_string;
                    switch (direction) {
                        case 'next':
                            url_string = this.info.next;
                            break;
                        case 'previous':
                            url_string = this.info.previous;
                            break;
                        default:
                            return this.$route.query.page
                    }

                    const url = new URL(url_string);
                    return url.searchParams.get('page')
                }
                catch (err) {
                    return
                }
            },
            // 获取文章列表数据
            get_article_data: function () {
                let url = '/api/article';
                const page = Number(this.$route.query.page);
                if (!isNaN(page) && (page !== 0)) {
                    url = url + '/?page=' + page;
                }

                axios
                    .get(url)
                    .then(response => (this.info = response.data))
            }
        },
        watch: {
            // 监听路由是否有变化
            $route() {
                this.get_article_data()
            }
        }
    }

</script>

...

改动内容较多,让我们逐个拆解。

// 判断页面是否存在
is_page_exists(direction) {
    if (direction === 'next') {
        return this.info.next !== null
    }
    return this.info.previous !== null
},
...

这里就可以看出参数的作用了,根据参数不同确定所要查询页码的方向,返回不同的数据。

// 获取页码
get_page_param: function (direction) {
    try {
        let url_string;
        switch (direction) {
            case 'next':
                url_string = this.info.next;
                break;
            case 'previous':
                url_string = this.info.previous;
                break;
            default:
                return this.$route.query.page
        }

        const url = new URL(url_string);
        return url.searchParams.get('page')
    }
    catch (err) {
        return
    }
},
...
  • try 是为了避免潜在的取值问题(比如网速缓慢时 info 还未获取到数据);一般来说 catch 语句应该含有对应报错的措施,教程就略过了。
  • switch 同样是用来控制翻页方向,有点点不同的是它默认查询了当前的页码,用于显示。
  • 根据翻页方向,构建 URL 对象并获取到其中的页码参数。
// 获取文章列表数据
get_article_data: function () {
    let url = '/api/article';
    const page = Number(this.$route.query.page);
    if (!isNaN(page) && (page !== 0)) {
        url = url + '/?page=' + page;
    }

    axios
        .get(url)
        .then(response => (this.info = response.data))
}

把获取数据的逻辑抽离为一个单独的方法,它根据当前的页码,向后端查询对应的数据。如果页码不存在,则返回首页。因此 mounted() 修改为调用此方法就可以了。

watch: {
    // 监听路由是否有变化
    $route() {
        this.get_article_data()
    }
}
...

这个 watch 就非常重要了,划重点。它的作用是监听 Vue 管理的数据,一旦发生变化就执行对应的方法。比如这里,我们已经知晓 this.$route 是 Vue 的路由对象了,因此将其注册到 watch 中,每当其变化(也就是 url 中的页码参数 ?page 变化了)则立即根据当前页码更新对应的文章数据。

你可能会问,既然首页的文章数据是在页面初始化时通过 mounted() 加载的,那为什么翻页后的数据不在 mounted() 中更新?很遗憾这是不行的。因为参数变化在 vue-router 看来不算是真正的路径变化,因此不会触发 mounted() 这类生命周期钩子。

关于 watch 更多内容请见文档

样式

样式改动部分如下:

<!--  frontend/src/components/ArticleList.vue  -->

...

<style scoped>
    ...

    #paginator {
        text-align: center;
        padding-top: 50px;
    }

    a {
        color: black;
    }

    .current-page {
        font-size: x-large;
        font-weight: bold;
        padding-left: 10px;
        padding-right: 10px;
    }

</style>

CSS 样式通常都很直白,没有多少可讲的知识点,因此这里就简单贴出来。

若你有更好看的外观解决方案,大胆的更改,通常样式不影响功能,只影响个人审美。

测试

完成之后,启动前后端服务器,看一眼成果:

注意看页面和 url 是如何发生变化的。

课后作业

新手写代码时 90% 的错误都是低级的,比如忘记启动后端服务器、忘记配置参数、没注意跨域问题等等之类的。那么如果开发时页面没获取到后端数据,一片空白通常让人无所适从。

本章用到的 try...catch 捕获代码中可能出现的错误,类似的能力 axios 也有:

axios
    .get('https://xxx.com/api/...')
    .then(...)
    .catch(error => console.log(error))

.catch 到错误后,可以简单粗暴打印到控制台,也可以专门做一个调试信息显示到页面上,提示你错误的原因。完善代码中可能会出错的地方(并给开发者亲切的错误反馈),就是课后作业了,可参考文档

另外很重要的就是出错后一定要多多查看前后端的控制台,逐字逐句阅读报错信息。切记。




本文作者: 杜赛
发布时间: 2021年03月22日 - 14:05
最后更新: 2023年10月16日 - 14:06
转载请保留原文链接及作者