写在前面
依据项目需求,实现了上传图片的基础功能。前端发送的data类型是Formdata。不用额外配置headers的Content-Type。
优化诸如:文件格式限制为图片格式、文件分片等没有涉及。
后续搭建完架构以后再进行优化。
后端
安装依赖
npm install --save multer
- UserRouter.js中添加:
// 图片上传
const multer = require('multer')
// 图片存储路径
const upload = multer({dest:'public/avataruploads/'})
UserRouter.post('/adminapi/upload', upload.single('file'),UserController.upload);
UserController添加新方法
注意:后端此处直接将图片和前端本地地址拼接后返回(config)。所以前端不需要重复进行图片地址拼接。
const config = "http://localhost:3000"
...
upload: async (req, res) => {
const { username, introduction, gender } = req.body
const token = req.headers["authorization"].split(" ")[1]
const avatar = req.file ? config + `/avataruploads/${req.file.filename}` : ''
var payload = JWT.verify(token)
var result = await UserService.upload({ id: payload.id, username, introduction, gender: Number(gender), avatar })
if (avatar) {
res.send({
code: '1',
data: {
username, introduction, gender: Number(gender), avatar
}
})
} else {
res.send({
code: '1',
data: {
username, introduction, gender: Number(gender)
}
})
}
}
UserService中添加
upload: async ({ id, username, introduction, gender, avatar }) => {
if (avatar) {
const searchSQL = `UPDATE user
SET username='${username}',
introduction='${introduction}',
gender='${gender}',
avatar='${avatar}'
WHERE id='${id}'`;
return connectionQuery(searchSQL);
} else {
const searchSQL = `UPDATE user
SET username='${username}',
introduction='${introduction}',
gender='${gender}'
WHERE id='${id}'`;
return connectionQuery(searchSQL);
}
}
前端
表单及类型定义
export interface ChangeInfoReq{
username:String,
gender:Number,
introduction:String,
avatar:String,
file:File|null
}
const { username, gender, avatar, introduction } = userStore.user
const userForm = ref<ChangeInfoReq>({
username,
gender,
avatar,
introduction,
file: null
})
新增表单提交函数
const handelChange = (file: File) => {
userForm.value.avatar = URL.createObjectURL(file)
userForm.value.file = file
}
const UpdateHandle = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid: boolean) => {
if (valid) {
const params = new FormData()
if (userForm.value.file !== null) {
params.append("file", userForm.value.file)
}
params.append("username", userForm.value.username as string)
params.append("avatar", userForm.value.avatar as string)
params.append("gender", String(userForm.value.gender))
params.append("introduction", userForm.value.introduction as string)
const res = await doUpdateUserAPI(params)
if (res.data.code === "1") {
ElMessage({ type: 'success', message: '修改成功' })
userStore.changeInfo(res.data.data)
} else {
ElMessage({ type: 'error', message: '修改失败' })
}
}
})
}
二次封装头像上传组件
<script setup lang="ts">
import { defineProps,computed,defineEmits } from 'vue';
import { Plus } from '@element-plus/icons-vue'
const props = defineProps({
avatar:String
})
const emit = defineEmits(["NoahChange"])
const handelChange = (file:any) =>{
emit("NoahChange",file.raw)
}
</script>
<template>
<el-upload
class="avatar-uploader"
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
:show-file-list="false"
:auto-upload="false"
:on-change="handelChange"
>
<img v-if="props.avatar" :src="props.avatar" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</template>
<style scoped lang="scss">
.avatar-uploader .avatar {
width: 178px;
height: 178px;
display: block;
}
::v-deep(.el-upload) {
border: 1px dashed var(--el-border-color);
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 :avatar="userForm.avatar" @NoahChange="handelChange" />
踩坑
req.body为{}
原因:请求头里面的content-type格式Content-Type: multipart/form-data
。
前端请求头设置了服务端无法进行解析的MIME类型,导致服务端无法解析请求体,从而req.body显示为空
解决方法:在app.js中先挂载router后进行路由跳转判断
app.use(UserRouter);
app.use((req, res, next) => {
// token有效,next放行
// token过期,返回401
// 例外情况:处于登录页面,原本就没有token
if (req.url === "/adminapi/user/login") {
next()
return
}
const token = req.headers["authorization"].split(" ")[1]
console.log(req.body)
...
}