Django-Vue搭建个人博客:标题图的提交

5220 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() 方法,在保存数据前执行压缩的逻辑。
  • 教程只完成了发表新文章时添加标题图,至于更新文章时的代码就交给读者自行摸索了。套路都是一样的,聪明的你应该有这个举一反三的能力。

最后一提,对于个人博客来说,服务器的存储空间、带宽都是宝贵的资源。多媒体文件通常应该放到专业的对象存储云服务商中,最好不要老老实实上传到自己服务器。这又牵扯到博客和对象存储云服务商的接口调用问题,不过这又是另外一个话题了。

或者就手动上传至对象存储云服务器中吧,对产量小的博客来说也没那么麻烦。




本文作者: 杜赛
发布时间: 2021年03月30日 - 11:22
最后更新: 2023年10月17日 - 11:23
转载请保留原文链接及作者