Django-Vue搭建个人博客:标题图的提交
4507 views, 2021/03/30 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()
方法,在保存数据前执行压缩的逻辑。 - 教程只完成了发表新文章时添加标题图,至于更新文章时的代码就交给读者自行摸索了。套路都是一样的,聪明的你应该有这个举一反三的能力。
最后一提,对于个人博客来说,服务器的存储空间、带宽都是宝贵的资源。多媒体文件通常应该放到专业的对象存储云服务商中,最好不要老老实实上传到自己服务器。这又牵扯到博客和对象存储云服务商的接口调用问题,不过这又是另外一个话题了。
或者就手动上传至对象存储云服务器中吧,对产量小的博客来说也没那么麻烦。