模版部分:
鼠標懸浮到頭像的部分就出現下拉框顯示可以修改頭像,
el-upload是隱藏的,可能只是為了實現on-change函數和before-upload函數吧
這塊做的確實有點馬虎了。
<div class="r-content"><el-dropdown><span class="el-dropdown-link"><img :src="getImageUrl" class="avatar"></span><template #dropdown><el-dropdown-menu><el-dropdown-item @click="handleUpdateAvatar">修改頭像</el-dropdown-item><el-dropdown-item @click="handleLoginOut">退出登錄</el-dropdown-item></el-dropdown-menu></template></el-dropdown><el-uploadclass="avatar-uploader"action="#":show-file-list="true":on-change="handleAvatarChange":before-upload="beforeAvatarUpload"style="display: none;"ref="uploadRef"><!-- <el-button ref="btn" size="large" type="primary">選取文件</el-button> --></el-upload></div>
import { ref, computed,nextTick } from 'vue'
import { useAllDataStore } from '../stores'
import { useRouter } from 'vue-router'
// import { ElMessage,ElUpload } from 'element-plus'
import { handleAvatarChange } from '@/services/editService.js'
const store = useAllDataStore()
import defaultAvatar from '@/assets/images/user-default.png'
const getImageUrl = computed(() => {return store.state.avatar||defaultAvatar;
})
// 監聽狀態變化(調試用)
watch(() => store.state.avatar,(newVal, oldVal) => {console.log('頭像更新了:', newVal, oldVal)},{ immediate: true } // 立即執行一次
)
const uploadRef = ref(null)
function handleUpdateAvatar() {console.log('handleUpdateAvatar')uploadRef.value.$el.querySelector('input').click()
}// 上傳前的校驗,比如限制文件類型、大小等
function beforeAvatarUpload(file) {const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';const isLt2M = file.size / 1024 / 1024 < 2; if (!isJPG) {ElMessage.error('上傳頭像只能是 JPG/PNG 格式!');}if (!isLt2M) {ElMessage.error('上傳頭像大小不能超過 2MB!');}return isJPG && isLt2M;
}//跨組件之間的傳值
const router = useRouter()
const handleLoginOut = () => {store.clean();router.push('/login')
}
前端發送請求部分:
export const handleAvatarChange=async (file)=> {try {// 創建 FormData 對象,用于上傳文件const formData = new FormData();formData.append('file', file.raw); // 調用后端接口上傳頭像,這里的接口地址根據實際后端定義填寫const res = await axios.post(`${API_URL}/updateAvatar`, formData, {headers: {'Content-Type': 'multipart/form-data', 'Authorization':`Bearer ${localStorage.getItem('token')}`,},withCredentials: true});if (res.data.code === 200) { // 假設后端返回 code 為 200 表示成功ElMessage.success('頭像修改成功');// 更新 store 中的頭像地址store.updateImg(res.data.data.avatar);}} catch (error) {// console.error('上傳頭像出錯!:', error);ElMessage.error('網絡異常,頭像修改失敗!');}
}
后端處理請求部分:
import { Router } from 'express';
import multer from 'multer';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs/promises';
import pool from '../config/db.js';
import jwt from 'jsonwebtoken';
import { JWT_SECRET } from '../config/config.js';
import bodyParser from 'body-parser';
const router = Router();// 1. 直接通過 import.meta.url 計算上傳目錄路徑(不使用 __dirname)
const currentFileUrl = new URL(import.meta.url); //當前文件完整的地址
const currentDirPath = path.dirname(fileURLToPath(currentFileUrl)); // 當前文件所在目錄routes的地址
const uploadDir = path.join(currentDirPath, '../public/avatars'); // 拼接上傳目錄路徑
// console.log('uploadDir:',uploadDir);
// 初始化上傳目錄
try {await fs.access(uploadDir);
} catch {await fs.mkdir(uploadDir, { recursive: true });
}// 2. 配置 multer 存儲規則
const storage = multer.diskStorage({destination: (req, file, cb) => {cb(null, uploadDir);},filename: (req, file, cb) => {const safeName = file.originalname.replace(/[^a-zA-Z0-9_.-]/g, '');const uniqueName = `${Date.now()}-${safeName}`;cb(null, uniqueName);//生成唯一文件名}
});// 3. 創建 multer 實例
const upload = multer({limits: { fileSize: 2 * 1024 * 1024 },fileFilter: (req, file, cb) => {if (file.mimetype.startsWith('image/')) {cb(null, true);} else {cb(new Error('只允許上傳圖片文件!'), false);}},storage: storage
});// 4.添加認證中間件
const authenticate = (req, res, next) => {const token = req.headers.authorization?.split(' ')[1];console.log(req.headers);if (!token) {return res.status(401).json({ code: 401, message: '未提供Token認證信息!' });}try {// 獲取發送請求方的token信息,驗證發送人 同時在post請求中順利的修改請求人的數據庫字段const decoded = jwt.verify(token, JWT_SECRET);req.user = decoded;console.log('req.user:',req.user);next();} catch (error) {res.status(401).json({ code: 401, message: '這是無效或者過期的Token!' });
}
}
// 5. 處理頭像上傳請求
router.post('/updateAvatar',authenticate, upload.single('file'), async (req, res) => {try {if (!req.file) {return res.status(400).json({ code: 400, message: '文件為空,請選擇要上傳的頭像!' });}console.log('req.file.filename:',req.file.filename);// 生成圖片訪問 URLconst avatarUrl = `http://localhost:3000/avatars/${req.file.filename}`;// 驗證登錄態if (!req.user?.id) {return res.status(401).json({ code: 401, message: '未登錄,無法修改頭像' });}const userId = req.user.id;// 更新數據庫const [results] = await pool.query('UPDATE users SET avatarUrl = ? WHERE id = ?',[avatarUrl, userId]);if (results.affectedRows === 0) {return res.status(404).json({ code: 404, message: '用戶不存在,更新失敗' });}res.status(200).json({code: 200,message: '頭像修改成功',data: { avatar: avatarUrl }});} catch (error) {console.error('頭像上傳失敗:', error);res.status(500).json({ code: 500, message: '服務器錯誤,上傳失敗' });}
});
因為有文件處理中間件這些的吧所以確實麻煩了些,還有認證中間件,可以自行刪減,注意頭像要上傳原圖片,然后對應的格式就是multipart/form-data了 這塊content-type我這個項目里