Django-Vue搭建个人博客:文章列表

14097 views, 2021/03/20 updated   Go to Comments

本章就正式开始写基于 Vue 3 的前端页面了,具体来说就是编写一个简洁的文章列表页面,激动不激动。

准备工作

安装Axios

虽然现在前后端 Django + Vue 都有了,但还缺一个它们之间通信的手段。Vue 官方推荐的是 axios 这个前端库。

命令行进入 frontend 目录,安装 axios:

> npm install axios

喝口茶就安装完成了。

解决跨域

跨域问题是由于浏览器的同源策略(域名,协议,端口均相同)造成的,是浏览器施加的安全限制。说简单点,Vue 服务器端口(8080)和 Django 服务器端口(8000)不一致,因此无法通过 Javascript 代码请求后端资源。

解决办法有两种。

第一种方法是创建 frontend/vue.config.js 文件并写入:

module.exports = {
    devServer: {
        proxy: {
            '/api': {
                target: `http://127.0.0.1:8000/api`,
                changeOrigin: true,
                pathRewrite: {
                    '^/api': ''
                }
            }
        }
    }
};

这个 Vue 的配置文件给前端服务器设置了代理,即将 /api 地址的前端请求转发到 8000 端口的后端服务器去,从而规避跨域问题。

另一种方法是在后端引入 django-cors-middleware 这个库,在后端解决此问题。

此方法具体步骤百度很多,就不赘述了。

两种解决方法都可以,本文将选择第一种即前端代理的方法。

Vue结构

本教程假定读者已经具有了 Javascript / Html / Css 等前端基础知识,因此不会展开讲相关内容。但为了理解 Vue 的基本结构,让我们来看三个重要的文件。

index.html

此文件路径位于 frontend/public/index.html,内容如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    ...
  </head>
  <body>
    ...
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

这个页面是整个前端工程提供 html 的入口,里面的 <div id="app"> 是 Vue 初始化应用程序的根容器。

不过在前端工程化的思想中,我们很少会直接去写这类 html 文件。

main.js

此文件位于 frontend/src/main.js ,内容如下:

import {createApp} from 'vue'
import App from './App.vue'

createApp(App).mount('#app');

它的作用就是把后续你要写的 Vue 组件挂载到刚才那个 index.html 中。

如果你有些前端的初始化配置,都可以写到这里。

App.vue

此文件位于 frontend/src/App.vue ,内容如下:

<template>
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

<script>
    import HelloWorld from './components/HelloWorld.vue'
    export default {
        name: 'App',
        components: {
            HelloWorld
        }
    }
</script>

<style>
    #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
    }
</style>

仔细看一下,这个文件似乎就是对应 Vue 的欢迎页面嘛!

Vue 采用组件化的思想,把同一个组件的内容打包到一起。比如这个默认的 App.vue 文件,明显 <template> 标签就对应传统的 html<script> 标签对应 javascript<style> 标签对应了 css

<HelloWorld .../> 这个就是一个封装好的组件,路径位于 frontend/src/components/HelloWorld.vue

以上就是 Vue 项目三个重要的文件,而对入门者来说,最重要的就是各种 .vue 文件,这就是你最主要的写代码的地方。

翻一翻 frontend 中的每个文件,感受 Vue 项目的结构。

文章列表

接下来就实际尝试一下编写文章列表页面了,通常这也是博客的首页。

首先把 App.vue 中的默认代码都删掉,写入以下代码:

<!--   frontend/src/App.vue   -->

<template>
    <div v-for="article in info.results" v-bind:key="article.url" id="articles">
        <div class="article-title">
            {{ article.title }}
        </div>
    </div>
</template>

<script>
    import axios from 'axios';

    export default {
        name: 'App',
        data: function () {
            return {
                info: ''
            }
        },
        mounted() {
            axios
                .get('/api/article')
                .then(response => (this.info = response.data))
        }
    }
</script>

<style>
    #articles {
        padding: 10px;
    }

    .article-title {
        font-size: large;
        font-weight: bolder;
        color: black;
        text-decoration: none;
        padding: 5px 0 5px 0;
    }
</style>

像前面说的一样,Vue 把同一个组件的 template / script / style 打包到一起。

让我们先从 <script> 脚本看起。

script

当一个 Vue 实例被创建时,它将 data 对象返回的所有属性加入到 Vue 的响应式系统中。更神奇的是,当这些属性的值发生改变时,视图将会产生“响应”,即自动更新为新的值。

比方说上面代码的 data 中的 info 属性在初始化时赋值了一个空字符串。当 Vue 加载完成后调用了生命周期的钩子 mounted() 方法,通过 axios 向 Django 后端获取到文章列表数据并赋值给 info 后,页面中关联的部分也会立即随之更新,而不用你手动去操作页面元素,这就是响应式的好处。

Axios 自动将请求得到的 Json 数据转换为 JavaScript 对象,所以你可以直接调用接口里的数据了。

如果写好上述代码,访问 http://localhost:8080/ 看不到任何内容,请检查后端服务器是否已启动,并查看浏览器控制台确保没有跨域相关的报错。

template

如果你之前学过 Django 内置的模板语法,那么 Vue 的模板语法就不难理解。元素块中以 v 打头的属性即是 Vue 的模板语法标记。 v-for 即循环可迭代元素(info.results 对应后端数据的 json 结构。请对照后端接口进行理解。),v-bind:key 给定了循环中每个元素的主键,作用是方便 Vue 渲染时对元素进行识别。

注意,很巧的是 Vue 默认同样也用双花括号{{ }} 定义它所持有的数据对象。所以这里的双花括号和 Django 模板语法没有任何关系,千万别搞混了。

style

这部分纯粹就是 css 了,也就是规定了页面各元素的大小、位置、颜色等样式,比较基础就不展开讲了。

顺利的话(别忘了前后端服务器都要启动),现在你的页面应该时这样子:(通过后台添加一些测试文章)

虽然很简陋,但是成功把文章列表数据渲染出来了。

优化界面

继续给列表数据添加内容,比如显示后端辛辛苦苦开发的标签和创建时间:

<!--   frontend/src/App.vue   -->

<template>

    ...

    <div v-for="...">
        <div>
            <span 
                  v-for="tag in article.tags" 
                  v-bind:key="tag" 
                  class="tag"
            >
                {{ tag }}
            </span>
        </div>

        <div class="article-title">...</div>

        <div>{{ formatted_time(article.created) }}</div>
    </div>

</template>

<script>
    ...
    export default {
        ...
        data: function () {...},
        mounted() {...},    // 注意添加这个逗号!
        methods: {
            formatted_time: function (iso_date_string) {
                const date = new Date(iso_date_string);
                return date.toLocaleDateString()
            }
        }
    }
</script>

<style>
    ...

    .tag {
        padding: 2px 5px 2px 5px;
        margin: 5px 5px 5px 0;
        font-family: Georgia, Arial, sans-serif;
        font-size: small;
        background-color: #4e4e4e;
        color: whitesmoke;
        border-radius: 5px;
    }
</style>

标签 tag 和文章标题类似,用 v-for 循环取值即可。

创建时间时 article.created 由于需要格式化,则用到点新东西:方法(即methods,注意看 scripts 中对其的定义)。方法名为 formatted_time() ,功能很简单,即把 iso 日期转换为人类容易理解的日期显示形式。

方法 methods 既可以在脚本中直接调用,也可以在模板中通过标签属性或者花括号调用,非常方便。

刷新页面,可以看到标签和日期都成功显示出来了。

记得在后台中添加适当的标签数据哦。

页眉和页脚

博客网站有页眉和页脚才比较美观,因此继续添加这部分内容:

<!--   frontend/src/App.vue   -->

<template>

    <div id="header">
        <h1>My Drf-Vue Blog</h1>
        <hr>
    </div>


    <div v-for="..." id="articles">...</div>


    <div id="footer">
        <p>dusaiphoto.com</p>
    </div>

</template>

<style>
    #app {
        font-family: Georgia, Arial, sans-serif;
        margin-left: 40px;
        margin-right: 40px;
    }

    #header {
        text-align: center;
        margin-top: 20px;
    }

    #footer {
        position: fixed;
        left: 0;
        bottom: 0;
        height: 50px;
        width: 100%;
        background: whitesmoke;
        text-align: center;
        font-weight: bold;
    }

    ...

</style>

没有新知识,唯一需要注意的就是样式中的 #app ,它是由 Vue 自动挂载的,因此覆盖了整个页面的元素。

现在你的博客页面是这样子的:

是不是有点博文列表的意思了?

组件化

组件化是 Vue 的核心思想之一。组件可以把网页分解成一个个的小功能,达到代码解耦及复用。

frontend/src/components/ 路径下分别创建 ArticleList.vue / BlogHeader.vue / BlogFooter.vue 三个文件,并且把我们之前在 App.vue 中写的代码分别搬运到对应的位置。

三个文件的内容如下(注意 export 中的 name 有对应的更改):

ArticleList.vue:

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

<template>
    <div v-for="article in info.results" v-bind:key="article.url" id="articles">
        <div>
            <span
                  v-for="tag in article.tags" 
                  v-bind:key="tag" 
                  class="tag"
            >
                {{ tag }}
            </span>
        </div>
        <div class="article-title">
            {{ article.title }}
        </div>
        <div>{{ formatted_time(article.created) }}</div>
    </div>

</template>

<script>
    import axios from 'axios';

    export default {
        name: 'ArticleList',
        data: function () {
            return {
                info: ''
            }
        },
        mounted() {
            axios
                .get('/api/article')
                .then(response => (this.info = response.data))
        },
        methods: {
            formatted_time: function (iso_date_string) {
                const date = new Date(iso_date_string);
                return date.toLocaleDateString()
            }
        }
    }

</script>

<!-- "scoped" 使样式仅在当前组件生效 -->
<style scoped>
    #articles {
        padding: 10px;
    }

    .article-title {
        font-size: large;
        font-weight: bolder;
        color: black;
        text-decoration: none;
        padding: 5px 0 5px 0;
    }

    .tag {
        padding: 2px 5px 2px 5px;
        margin: 5px 5px 5px 0;
        font-family: Georgia, Arial, sans-serif;
        font-size: small;
        background-color: #4e4e4e;
        color: whitesmoke;
        border-radius: 5px;
    }
</style>

BlogHeader.vue:

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

<template>
    <div id="header">
        <h1>My Drf-Vue Blog</h1>
        <hr>
    </div>
</template>

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

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

BlogFooter.vue:

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

<template>
    <!--  br 标签给页脚留出位置  -->
    <br><br><br>
    <div id="footer">
        <p>dusaiphoto.com</p>
    </div>
</template>

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

<style scoped>
    #footer {
        position: fixed;
        left: 0;
        bottom: 0;
        height: 50px;
        width: 100%;
        background: whitesmoke;
        text-align: center;
        font-weight: bold;
    }
</style>

搬运完成后,最后将 App.vue 修改为如下:

<!--  frontend/src/App.vue  -->

<template>

    <BlogHeader/>

    <ArticleList/>

    <BlogFooter/>

</template>

<script>
    import BlogHeader from './components/BlogHeader.vue'
    import BlogFooter from './components/BlogFooter.vue'
    import ArticleList from './components/ArticleList.vue'

    export default {
        name: 'App',
        components: { BlogHeader, BlogFooter, ArticleList }
    }
</script>

<style>
    #app {
        font-family: Georgia, Arial, sans-serif;
        margin-left: 40px;
        margin-right: 40px;
    }
</style>

刷新页面,功能虽然与修改前完全相同,但代码变得更加规整和清爽了。




本文作者: 杜赛
发布时间: 2021年03月20日 - 17:39
最后更新: 2021年03月20日 - 17:39
转载请保留原文链接及作者