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

文件上传(图片上传)

前端:Vue3 + element-plus

后端:express

前端

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

Upload 组件

js 复制代码
  <!-- 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>

Upload 方法

js 复制代码
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
js 复制代码
 <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('更新成功')
      }
    }
  });
}
// ...

后端

js 复制代码
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;
js 复制代码
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,
        }
      })
    }
  }
js 复制代码
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. 先计算原本有没有图片,有则展示原图片的 url,没有则展示 icon
  2. 点击上传组件从本地选取图片,单个图片上传可以控制选取后自动上传和之后手动上传
  3. 当统一手动上传时,选择完图片之后将选择的图片file文件转换为URL地址进行前端页面的回显,并将其原本file格式的文件赋值给form表单内容对象,不修改头像默认使用原图片的 url
  4. 将form表单内容对象转化为 FormData 的格式,并设置 FormData 请求头,作为参数传递给后端
  5. 后端 multer 中间件用于处理 form-data 数据(文件上传)
  6. 将图片文件存入后端静态资源文件
  7. 数据响应,如果没有图片的更改即不传递图片文件数据给前端(如果传递则为空,会覆盖页面原本存在的头像url),这样前端默认使用已存在的blob文件
相关推荐
passerby606129 分钟前
完成前端时间处理的另一块版图
前端·github·web components
掘了36 分钟前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅39 分钟前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment1 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
吹牛不交税2 小时前
admin.net-v2 框架使用笔记-netcore8.0/10.0版
vue.js·.netcore