• 用户头像(图片文件)上传(Vue + nodejs 前后端)


    文件上传(图片上传)

    前端:Vue3 + element-plus

    后端:express

    前端

    封装一个 Upload 组件和一个 upload 方法。
    Upload 组件

      <!-- auto-upload 选择好图片后立刻自动上传后端还是手动点击某按钮上传后端 -->
      <el-upload
        class="avatar-uploader"
        action="https://jsonplaceholder.typicode.com/posts/"
        :show-file-list="false"
        :auto-upload="false"
        :on-change="handleUpload"
      >
        <img
          v-if="props.avatar"
          :src="uploadAvatarUrl"
          class="avatar"
        />
        <el-icon
          v-else
          class="avatar-uploader-icon"
        >
          <Plus />
        </el-icon>
      </el-upload>
    </template>
    
    <script setup>
    import { Plus } from '@element-plus/icons-vue'
    import { computed } from 'vue'
    const props = defineProps({
      avatar: String
    })
    const emit = defineEmits(['handle'])
    // 每次选择完图片之后的回调,file是文件信息,file.raw是原生文件对象
    const handleUpload = (file) => {
      emit('handle', file.raw)
    }
    const uploadAvatarUrl = computed(() => {
      return props.avatar.includes('blob') ? props.avatar : 'http://localhost:3000' + props.avatar
    })
    </script>
    
    <style scoped lang="scss">
    // 由于 scoped 属性,局部作用域,所以需要通过 ::v-deep 操作符来深层设置嵌套样式
    ::v-deep .el-upload {
      border: 1px dashed #d9d9d9;
      border-radius: 6px;
      cursor: pointer;
      position: relative;
      overflow: hidden;
      transition: var(--el-transition-duration-fast);
    }
    
    ::v-deep .el-upload:hover {
      border-color: var(--el-color-primary);
    }
    
    ::v-deep .el-icon.avatar-uploader-icon {
      font-size: 28px;
      color: #8c939d;
      width: 178px;
      height: 178px;
      text-align: center;
    }
    
    .avatar {
      width: 178px;
      height: 178px;
    }
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    Upload 方法

    import axios from "axios"
    const upload = (path, userForm) => {
      // console.log('submit', userForm)
      // 上传文件需要转化为 FormData 的格式
      const params = new FormData()
      for (let item in userForm) {
        params.append(item, userForm[item])
      }
      return axios.post(path, params, {
        // FormData 文件上传的请求头
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      }).then(res=>res.data)
    }
    export default upload
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
     <UploadAvatar
        :avatar="userForm.avatar"
        @handle="handleUpload"
      ></UploadAvatar>
      
      // ...
      const userForm = reactive({
      username,
      gender,
      introduction,
      // avatar 和 file 都是代表的用户头像 avatar 用于前端用户回显,file 用户提交后端
      avatar,
      file: null
    });
    // 每次选择完图片之后的回调
    const handleUpload = (file) => {
      userForm.avatar = URL.createObjectURL(file);
      userForm.file = file;
    }
    // 提交个人信息表单
    const submitForm = () => {
      userFormRef.value.validate(async (valid) => {
        if (valid) {
          const res = await upload('/adminapi/user/upload', userForm)
          if (res.ActionType === 'OK') {
            store.commit('changeUserInfo', res.data)
            ElMessage.success('更新成功')
          }
        }
      });
    }
    // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    后端

    const express = require('express');
    const UserRouter = express.Router();
    const UserController = require('../../controllers/admin/UserController')
    // multer 中间件用于处理 form-data 数据(文件上传)
    const multer = require('multer')
    const upload = multer({ dest: 'public/avataruploads/' })
    
    UserRouter.post('/adminapi/user/login', UserController.login)
    UserRouter.post('/adminapi/user/upload', upload.single('file'), UserController.upload)
    
    module.exports = UserRouter;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    upload: async (req, res) => {
        // console.log(req.body,req.file)
        const { username, introduction, gender } = req.body
        const token = req.headers['authorization'].split(' ')[1]
        const payload = JWT.verify(token)
        const avatar = req.file ? `/avataruploads/${req.file.filename}` : ''
        await UserService.upload({ _id: payload._id, username, introduction, gender: Number(gender), avatar })
        if (avatar) {
          res.send({
            ActionType: 'OK',
            data: {
              username,
              gender: Number(gender),
              introduction,
              avatar,
            }
          })
        } else {
          res.send({
            ActionType: 'OK',
            data: {
              username,
              gender: Number(gender),
              introduction,
            }
          })
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    upload: async ({ _id,username, introduction,gender,avatar }) => {
      if(avatar) {
        return UserModel.updateOne({
          _id
        }, {
          username,
          introduction,
          gender,
          avatar
        })
      } else {
        return UserModel.updateOne({
          _id
        }, {
          username,
          introduction,
          gender
        })
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    总结

    用户头像文件上传流程:

    1. 先计算原本有没有图片,有则展示原图片的 url,没有则展示 icon
    2. 点击上传组件从本地选取图片,单个图片上传可以控制选取后自动上传和之后手动上传
    3. 当统一手动上传时,选择完图片之后将选择的图片file文件转换为URL地址进行前端页面的回显,并将其原本file格式的文件赋值给form表单内容对象,不修改头像默认使用原图片的 url
    4. 将form表单内容对象转化为 FormData 的格式,并设置 FormData 请求头,作为参数传递给后端
    5. 后端 multer 中间件用于处理 form-data 数据(文件上传)
    6. 将图片文件存入后端静态资源文件
    7. 数据响应,如果没有图片的更改即不传递图片文件数据给前端(如果传递则为空,会覆盖页面原本存在的头像url),这样前端默认使用已存在的blob文件
  • 相关阅读:
    如何在Spring Boot中配置双数据源?
    JSP中的九大隐式对象
    数据增强方法汇总
    结构模式匹配(Structural Pattern Matching)(一)匹配序列
    基于Vue+SpringBoot的厦门旅游电子商务预订系统 开源项目
    css 实现虚线效果的3种方式详解
    【pandas小技巧】--category类型补充
    咖啡屋时光书城【原创】
    批量修改视频尺寸:简单易用的视频剪辑软件教程
    Bags Game
  • 原文地址:https://blog.csdn.net/XiugongHao/article/details/136175428