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>
刷新页面,功能虽然与修改前完全相同,但代码变得更加规整和清爽了。