Django-Vue搭建个人博客:搜索文章

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

前面 Django 开发的部分已经实现了搜索接口,本章就基于此将其对应的搜索功能补充完整。

准备工作

为了让用户在博客的所有页面都能找到搜索框,将它放到页眉里是个不错的注意。

修改 BlogHeader.vue ,将搜索框的外观放进去:

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

<template>
    <div id="header">
        <div class="grid">
            <div></div>
            <h1>My Drf-Vue Blog</h1>
            <div class="search">
                <form>
                    <input type="text" placeholder="输入搜索内容...">
                    <button></button>
                </form>
            </div>
        </div>
        <hr>
    </div>
</template>

<script>
    export default {
        name: 'BlogHeader'
    }
</script>

<style scoped>
    #header {
        text-align: center;
        margin-top: 20px;
    }

    .grid {
        display: grid;
        grid-template-columns: 1fr 4fr 1fr;
    }

    .search {
        padding-top: 22px;
    }

    /* css 来源:https://blog.csdn.net/qq_39198420/article/details/77973339*/
    * {
        box-sizing: border-box;
    }

    form {
        position: relative;
        width: 200px;
        margin: 0 auto;
    }

    input, button {
        border: none;
        outline: none;
    }

    input {
        width: 100%;
        height: 35px;
        padding-left: 13px;
        padding-right: 46px;
    }

    button {
        height: 35px;
        width: 35px;
        cursor: pointer;
        position: absolute;
    }

    .search input {
        border: 2px solid gray;
        border-radius: 5px;
        background: transparent;
        top: 0;
        right: 0;
    }

    .search button {
        background: gray;
        border-radius: 0 5px 5px 0;
        width: 45px;
        top: 0;
        right: 0;
    }

    .search button:before {
        content: "搜索";
        font-size: 13px;
        color: white;
    }
</style>

代码大部分是在定义搜索框的外观,有兴趣的同学自行研究。

现在你的博客标题的右边应该就出现一个像模像样的搜索框了(无功能)。

接下来开始正式编写搜索的逻辑。

编程式导航

为了让搜索框发挥功能,继续修改 BlogHeader.vue

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

<template>
    ...
    <div class="search">
        <form>
            <input v-model="searchText" type="text" placeholder="输入搜索内容...">
            <button v-on:click.prevent="searchArticles"></button>
        </form>
    </div>
    ...
</template>

<script>
    export default {
        name: 'BlogHeader',
        data: function () {
            return {
                searchText: ''
            }
        },
        methods: {
            searchArticles() {
                const text = this.searchText.trim();
                if (text.charAt(0) !== '') {
                    this.$router.push({name: 'Home', query: { search: text }})
                }
            }
        }
    }
</script>

...
  • v-model 指令可以在表单控件上创建双向数据绑定。具体来说,就是上面的 <input> 中的数据和 Vue 管理的 searchText 数据绑定在一起了,其中一个发生变化,另一个也会改变。
  • v-on:click 绑定了按钮的鼠标点击事件,即点击则触发 searchArticles() 方法。.prevent 用于阻止按钮原本的表单提交功能。

前面章节我们用 <router-link> 标签实现了路由跳转。在必要时候路由跳转也可以通过脚本来动态实现,也就是上面代码的 this.$router.push(...) 了。注意 this.$routethis.$router ,前者代表路径对象,后者代表路由器对象。

总之,点击按钮触发 searchArticles() ,然后此方法将 searchText 作为参数跳转到新的路径。

正餐

页面跳转实现了,但是因为前面章节把 get_article_data() 方法中的 url 写死为 '/api/article' 了,所以跳转之后还不能够根据路径的中 search 参数展示筛选后的数据。因此要换个战场,在 ArticleList.vue 里进行修改(主要是 Javascript 部分)。

旧的翻页 <router-link> 仅考虑了路径参数中的 page 值。为了在翻页后取得包括 pagesearch 的正确路径,新写一个方法 get_path()

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

...

<script>
    ...

    export default {
        ...
        methods: {
            ...
            get_path: function (direction) {
                let url = '';

                try {
                    switch (direction) {
                        case 'next':
                            if (this.info.next !== undefined) {
                                url += (new URL(this.info.next)).search
                            }
                            break;
                        case 'previous':
                            if (this.info.previous !== undefined) {
                                url += (new URL(this.info.previous)).search
                            }
                            break;
                    }
                }
                catch { return url }

                return url
            }
        },
        ...
    }

</script>

...

如果下一页的路径存在,那么则返回其带参数的路径,否则就返回无任何参数的首页路径。

有了 get_path() 获取到路径后,还需要将路径用到请求数据的接口里。

修改 get_article_data() 方法,如下面这样:

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

...

<script>
    ...

    export default {
        ...
        methods: {
            ...
            get_article_data: function () {
                let url = '/api/article';

                let params = new URLSearchParams();
                // 注意 appendIfExists 方法是原生没有的
                // 原生只有 append 方法,但此方法不能判断值是否存在
                params.appendIfExists('page', this.$route.query.page);
                params.appendIfExists('search', this.$route.query.search);

                const paramsString = params.toString();
                if (paramsString.charAt(0) !== '') {
                    url += '/?' + paramsString
                }

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

</script>

...

这里的代码抛弃了之前用的字符串拼接的方式,改为专门用于处理路径参数的 URLSearchParams() 对象。为了将路径中已有的参数添加到 URLSearchParams() 中,可以用其本身的 append() 方法,但此方法不能判断值是否存在,从而获得类似 http://localhost:8080/?page=undefined 这种错误的路径。

解决方法可以在 methods 里写一个 appendIfExists() 方法,调用它来排除错误路径。还有一种方法是由于 JavaScript 是基于原型链的语言,因此可以通过原型链将此方法添加到已有对象中(包括内置原生对象),以扩展此对象的功能。

具体实施方法就是在 main.js 中写入:

// frontend/src/main.js

import ...

URLSearchParams.prototype.appendIfExists = function (key, value) {
    if (value !== null && value !== undefined) {
        this.append(key, value)
    }
};

createApp(App)...;

因为 main.js 在 Vue 初始化时必然会执行,如此一来URLSearchParams 对象就有了这个 appendIfExists() 了。

警告:以上示例仅供参考。请谨慎扩展原生类型,尤其是如果你的代码将被其他人使用,这可能导致意外的代码行为。建议在扩展方法的名称前加上一些标识符,以便潜在用户可以区分你注入的方法和原生的方法。

关于继承、原型链的解释,详见原型链。关于 URLSearchParams,详见URLSearchParams

把这些脚本写好后,就可以修改路由的模板了:

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

<template>
    ...


    <div id="paginator">
        <span v-if="...">
            <router-link :to="get_path('previous')">
                Prev
            </router-link>
        </span>
        <span class="...e">
            ...
        </span>
        <span v-if="...">
            <router-link :to="get_path('next')">
                Next
            </router-link>
        </span>
    </div>

</template>

...

:tov-bind:to 的简写,意思是“将 to 属性和 get_path(...) 的返回值保持一致”。如果不需要这种响应式行为,也可以 to="/abc" 这样直接赋值给属性。

v-bind 的用法详见文档

这就搞定了。随便搜索点东西看看效果:

翻页试试,看看路径和文章数据的变化,应该都是正常工作的。




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