web开发,上传下载文件是一个非常普遍的需求,但这一块牵扯到文件的存储、命名等,有很多知识点需要我们去掌握和练习!

最近帮一个客户定制一个校园类的OA系统,功能主要集中在微信端,也就是对接的微信公众号的一个项目,这个项目的后端我用了django框架来完成,当然,使用django框架是客户的要求,同时也是我比较熟悉的一个框架。

客户的要求是要在微信客户端内能上传文件以及压缩包,其实我个人感觉移动端上传图片的需求比较常见,但上传文件及压缩包这个功能并不常见,可以说就是个鸡肋,我不知道谁会在手机端压缩好文件进行上传,其次即使上传word一类的文件,应该在电脑端编辑好上传比较方便,如果再回到微信端去上传,我估计用户会骂娘。

不知道客户这奇葩需求是怎么考虑的,但作为开发的我们又有什么办法,为了钱,只能满足了。

这里插一句,不知道各位开发大佬在开发过程中都有遇到那些奇葩需求,可以吐槽吐槽!

好了,言归正传,实现需求我们是认真的,我这里前端使用了vue+axios+buefy来完成,vue和axios不过多解释,一对前端好搭档,buefy是一个非常简单但很好用的vue2 ui组件库,我个人非常喜欢,不但可以npm安装使用,并且还可以直接引入到html中,简单轻量,上手容易,郑重推荐给大家!

django的后端接口

首先来看一下我的后端接口代码

class AddTaskFileView(View):
    # 收文件接口
    def post(self, request, *args, **kwargs):
        user_id = request.POST.get('user_id')
        school_task_id = request.POST.get('school_task_id')
        # 必须使用request.FILES接收文件
        file_path = request.FILES.get('file_path')
        new_file_name = request.POST.get('new_name')
        print(new_file_name)
        
        school_task = SchoolTask.objects.get(id=int(school_task_id))
        user = User.objects.get(id=int(user_id))
        
        if not file_path:
            return JsonResponse({'code': 'err', 'message': '请选择有效的文件上传!'})
        
        # 处理文件名称
        ## 这里有个自定义文件名称的功能
        ## 如果传了自定义名称就是用自定义,负责使用默认构造的文件名称
        file_name_list = file_path.name.split('.')
        if new_file_name:
            file_path.name = f'{new_file_name}.{file_name_list[-1]}'
        else:
            print(user.code, user.name)
            if user.code and user.name and user.student_team:
                file_path.name = f'{user.student_team.name}{user.code}{user.nickname}.{file_name_list[-1]}'   
        
        # 保存文件
        RootIncludeFile.objects.create(school_task=school_task, student_owner=user, student_file=file_path)
        # 转到已上传
        hav_read = RootStudentHaveRead()
        hav_read.school_task = school_task
        hav_read.read_owner = user
        hav_read.is_read = True
        hav_read.save()
        return JsonResponse({'code': 'ok', 'message': '上传成功,即将跳转至已完成列表页!'})

这个后端接口非常简单,很容易就能看懂,我这里稍微解释一下几个比较特殊的地方,django后端接收文件数据必须使用request.FILES,这个大家一定要注意,否则获取不到数据。这里有一个特殊的需求就是,后端可以控制是否允许用户自定义文件名称,我这里做了处理,如果用户传过来的数据中,自定义名称存在,那么就是用自定义名称,否则就使用默认构造的名称,这一块就牵扯到前后端沟通的一个问题,自定义文件名称的表单何时出现交由前端处理!后端只是传递给配置数据!【这里我就不存在,因为我一人全干!😂😂】

再来看保存文件这里,我这里使用了一个模型专门保存用户上传的数据,这是因为除了保存文件还有一些和文件相关的信息需要保存并且获取,基于这个需求我们选择了使用模型来保存文件,当然,你也可以自己写保存文件的方法在这里替换即可!

前端处理

这里简单说一下,为什么要做这个进度条的功能,因为在异步提交文件的过程中,如果任何提示都没有,就会给用户造成一种我是不是上传按钮没点击上的错觉,造成重复提交多次,用户体验不好的同时,也有可能造成系统崩溃,尤其上传大文件!

前端主要代码

  1. 前端主要显示代码
<h1 class=" has-text-black has-text-weight-bold is-size-5 mb-3">提交文件</h1>
<!-- 进度条 默认隐藏-->
<b-progress 
  type="is-success" 
  :value=num 
  show-value
  format="percent"
  v-show="pShow"
></b-progress>

<!-- 选择文件表单 -->
<template>
  <b-field class="file" v-show="isShow">
    <b-upload v-model="file"  expanded>
      <a class="button is-primary is-fullwidth">
        <b-icon icon="upload"></b-icon>
        <span>[[  file.name || "请选择文件" ]]</span>
      </a>
    </b-upload>
  </b-field>
</template>

<!-- 是否允许自定义文件名表单 -->
{% if not_task.is_file_name %}
<b-field label="自定义文件名:">
  <b-input v-model="new_name"></b-input>
</b-field>
{% endif %}

<!-- 点击上传按钮  绑定点击上传事件 addTaskFile -->
<b-button 
  type="is-success" 
  expanded
  :disabled="dis"
  @click="addTaskFile">
  [[ btText ]]
</b-button>
  1. 前端vue相关处理方法及数据

我这里前端处理的思路可作为参考,当用户选择上传文件后,方可点击上传按钮,当用户点击上传按钮后,按钮置灰不可点击,按钮的文件变为“文件正在上传中...”,进度条出现,默认是隐藏的,上传完成后,等待1s,将按钮文字变为成功提示,并弹出成功提示消息,跳转到指定页面;否则,提示上传失败,刷新上传页面,提示重新上传!当然失败哪里也可以做不刷新处理,因为我们有Vue可以很方便的无刷新修改数据!【主要是因为我懒,没做!】

<script>
    var school_task_id = '{{ not_task.id }}';
    var user_id = '{{ request.user.id }}';
    var NotTask = new Vue({
        el: '#NotTask',
        delimiters: ['[[', ']]'],
        data: {
            file: {},
            new_name: '',
            num:0,
            dis: false,
            btText: '点击上传',
            isShow:true,
            pShow:false,
            isRedic: false
        },
        methods: {
            addTaskFile(){
                // 声明this
                let self = this;
                console.log('上传文件', this.file.path)
                let sendData = new FormData();
                sendData.append('file_path', self.file);
                sendData.append('school_task_id', school_task_id);
                sendData.append('user_id', user_id);
                sendData.append('new_name', self.new_name);
                axios({
                    url: '/h5/users/add_task/file/',
                    method: 'post',
                    data: sendData,
                    // 上传文件处理
                    onUploadProgress(progressEvent) {
                        if (progressEvent.lengthComputable) {
                            // 计算文件上传进度
                            let val = (progressEvent.loaded / progressEvent.total * 100).toFixed(0);
                            self.pShow = true
                            self.isShow = false
                            self.btText = "正在上传 "
                            self.dis = true
                            // 将进度数据赋值给进度条
                            self.num = parseInt(val)
                            // 通过判断进度到100后,代表完成,将允许跳转数据设置为true
                            if (self.num == 100) {
                                self.isRedic = true
                            } 
                            
                        }
                    },
                }).then(res => {
                    console.log(res)
                    if (self.isRedic){
                        // setTimeout(())
                        setTimeout(() => {
                            self.btText = "上传成功"
                            self.$buefy.toast.open({
                                message:  "上传成功",
                                type: 'is-success'
                            });
                            setTimeout(() => {
                                window.location.replace("/h5/owner/yes/task/")
                            }, 1000)
                        }, 1000)
                    }else{
                        this.$buefy.toast.open({
                            message: "上传失败,请重新上传!",
                            type: 'is-danger'
                        });
                        window.location.reload()
                    }
                })
            }
        }
    })
</script>

前端这里有几个重要的点需要注意

  1. 构造数据传递的方法包含文件一定要使用new FormData()
  2. vue2中关于this的指向一定要有了解,必须在上传方法开始通过一个变量声明this
  3. axios的上传文件数据获取不在then的回调中,而在config中,也就是在与url同级的数据结构中
onUploadProgress(progressEvent) {
  if (progressEvent.lengthComputable) {
    // 计算文件上传进度
     let val = (progressEvent.loaded / progressEvent.total * 100).toFixed(0);
     ...
       // 将进度数据赋值给进度条
       self.num = parseInt(val)
       console.log(parseInt(val))
      ...
  }
},

这段代码是axios获取文件数据的核心,记住他,使用axios上传文件搭配任何UI框架你都能处理进度条的功能,至于他是如何实现的,可以去看看axios的代码和说明,其实内部并不复杂,相比Ajax可以说有点简陋。

到这里我们的上传文件进度条的功能就完成了,看下演示动图吧!

欢迎大家关注学习,一起进步,笔者专注django相关开发,对django有深入研究,可一起学习探讨,并且承接django相关项目的开发任务!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐