Django-Vue搭建个人博客:文章列表
16849 views, 2023/10/16 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>
刷新页面,功能虽然与修改前完全相同,但代码变得更加规整和清爽了。