文件上传(图片上传)
前端:Vue3 + element-plus
后端:express
前端
封装一个 Upload 组件和一个 upload 方法。
Upload 组件
<!-- auto-upload 选择好图片后立刻自动上传后端还是手动点击某按钮上传后端 --><el-uploadclass="avatar-uploader"action="https://jsonplaceholder.typicode.com/posts/":show-file-list="false":auto-upload="false":on-change="handleUpload"><imgv-if="props.avatar":src="uploadAvatarUrl"class="avatar"/><el-iconv-elseclass="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 方法
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
<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('更新成功')}}});
}
// ...
后端
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;
upload: async (req, res) => {// console.log(req.body,req.file)const { username, introduction, gender } = req.bodyconst 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,}})}}
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})}
}
总结
用户头像文件上传流程:
- 先计算原本有没有图片,有则展示原图片的 url,没有则展示 icon
- 点击上传组件从本地选取图片,单个图片上传可以控制选取后自动上传和之后手动上传
- 当统一手动上传时,选择完图片之后将选择的图片file文件转换为URL地址进行前端页面的回显,并将其原本file格式的文件赋值给form表单内容对象,不修改头像默认使用原图片的 url
- 将form表单内容对象转化为 FormData 的格式,并设置 FormData 请求头,作为参数传递给后端
- 后端 multer 中间件用于处理 form-data 数据(文件上传)
- 将图片文件存入后端静态资源文件
- 数据响应,如果没有图片的更改即不传递图片文件数据给前端(如果传递则为空,会覆盖页面原本存在的头像url),这样前端默认使用已存在的blob文件