Django-Vue搭建个人博客:标题图的提交
5184 views, 2023/10/17 updated Go to Comments
文章的增删改查都搞定了,唯独剩标题图未进行处理(所有文章均无标题图)。莫慌,这章就来完成它。
有的读者一听到图片的提交上传就觉得麻烦,其实不是这样的。由于前面在写图片上传的后端接口时就已经把流程考虑完整了,因此标题图得处理就很简单了,甚至比前面的其他接口都要简单。
本章将以发表文章功能为例,讲解图片提交的实现。
发表文章页面
回顾一下图片提交的流程:在 multipart/form-data
中发送文件,然后将保存好的文件 id 返回给客户端。客户端拿到文件 id 后,发送带有 id 的 Json 数据,在服务器端将它们关联起来。
结合到 Vue 中就是:
- 在发表新文章页面中选定图片后,不等待文章的提交而是立即将图片上传。
- 图片上传成功后返回图片 id,前端将 id 保存待用。
- 提交文章时,将图片 id 一并打包提交即可。
根据这个思路,首先就要在 ArticleCreate.vue
中添加代码:
<!-- frontend/src/views/ArticleCreate.vue -->
<template>
...
<div id="article-create">
...
<!-- 添加一个新的 from -->
<form id="image_form">
<div class="form-elem">
<span>图片:</span>
<input
v-on:change="onFileChange"
type="file"
id="file"
>
</div>
</form>
<!-- 已有代码,提交文章数据的 from -->
<form>
...
</form>
</div>
...
</template>
<script>
...
export default {
...
data: function () {
return {
...
// 标题图 id
avatarID: null,
}
},
methods: {
onFileChange(e) {
// 将文件二进制数据添加到提交数据中
const file = e.target.files[0];
let formData = new FormData();
formData.append("content", file);
// 略去鉴权和错误处理的部分
axios
.post('/api/avatar/', formData, {
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': 'Bearer ' + localStorage.getItem('access.myblog')
}
})
.then( response => this.avatarID = response.data.id)
},
// 点击提交按钮
submit() {
...
authorization()
.then(function (...) {
if (...) {
let data = {...};
// 新增代码
// 添加标题图 id
data.avatar_id = that.avatarID;
...
}
...
}
)
},
...
}
}
</script>
- 新增了一个表单(不用表单其实也没关系),表单含有一个提交文件的控件;
v-on:change
将控件绑定到onFileChange()
方法,即只要用户选定了任何图片,都会触发此方法。 onFileChange(e)
中的参数为控件所触发的事件对象。由于图片二进制流不能以简单的字符串数据进行表示,所以将其添加到FormData
表单对象中,发送到图片上传接口。若接口返回成功,则将返回的 id 值保存待用。submit()
对应增加了图片 id 的赋值语句。
图片的上传、与文章的绑定就完成了。
你可以在发表页面自行选择图片尝试,打开控制台 network 面板,看看是否正确上传并且无任何报错。
显示标题图
标题图是能正确提交了,但现在还不能在文章列表页面显示它,因此需要修改 ArticleList.vue
代码。
一种比较美观的标题图显示方式为图片在左、文字在右。这就需要修改列表的网格结构,因此这里把对应的模板全部贴出来:
<!-- frontend/src/componenets/ArticleList.vue -->
<template>
<div v-for="article in info.results" ...>
<!-- 新增一个网格层 -->
<div class="grid" :style="gridStyle(article)">
<div class="image-container">
<img :src="imageIfExists(article)" alt="" class="image">
</div>
<!-- 下面是已有代码 -->
<div>
<div>
<span
v-if="article.category !== null"
class="category"
>
{{article.category.title}}
</span>
<span v-for="tag in article.tags" v-bind:key="tag" class="tag">{{ tag }}</span>
</div>
<div class="a-title-container">
<router-link
:to="{ name: 'ArticleDetail', params: { id: article.id }}"
class="article-title"
>
{{ article.title }}
</router-link>
</div>
<div>{{ formatted_time(article.created) }}</div>
</div>
</div>
</div>
...
</template>
<script>
...
export default {
...
methods: {
imageIfExists(article) {
if (article.avatar) {
return article.avatar.content
}
},
gridStyle(article) {
if (article.avatar) {
return {
display: 'grid',
gridTemplateColumns: '1fr 4fr'
}
}
},
...
},
...
}
</script>
<style scoped>
.image {
width: 180px;
border-radius: 10px;
box-shadow: darkslategrey 0 0 12px;
}
.image-container {
width: 200px;
}
.grid {
padding-bottom: 10px;
}
...
</style>
模板虽然新增代码不多,但是要注意:
- 网格层用
:style
将样式绑定到gridStyle()
方法,这主要是为了将有无标题图的文章渲染形式区分开。 img
元素将:src
绑定到imageIfExists()
方法,若文章有标题图则显示,无标题图则不显示。- 为了美观,在样式中限制图片的大小,使宽度一致。
没有其他新知识了,你应该已经轻车熟路了。
测试
首先看看发表文章页面:
上传文件的控件已经有了。当你选定好图片后,将会自动上传到服务器中(media文件夹中)。
发布几篇带有标题图的文章后,回主页看看效果:
任务完成,很轻松没骗你吧。
没有标题图的文章也能够正确排版,读者就自行测试了。
后续任务
还有一些收尾工作,教程就不再深入了,比如:
- 应该将上传文件的类型限制为仅图片。可以在控件中添加 accept 属性,像这样
<input ... accept="image/gif, image/jpeg" />
。除此之外,由于前端无秘密,为保险还应该在后端也进行对应的验证。 - 应该对图片尺寸进行限制和压缩,以免影响加载速度或占用过多空间。一种实现方式是覆写模型的
save()
方法,在保存数据前执行压缩的逻辑。 - 教程只完成了发表新文章时添加标题图,至于更新文章时的代码就交给读者自行摸索了。套路都是一样的,聪明的你应该有这个举一反三的能力。
最后一提,对于个人博客来说,服务器的存储空间、带宽都是宝贵的资源。多媒体文件通常应该放到专业的对象存储云服务商中,最好不要老老实实上传到自己服务器。这又牵扯到博客和对象存储云服务商的接口调用问题,不过这又是另外一个话题了。
或者就手动上传至对象存储云服务器中吧,对产量小的博客来说也没那么麻烦。