modified: begushiybashkir/bbvue/src/components/AvatarUpload.vue

modified:   begushiybashkir/bbvue/src/stores/auth.js
	modified:   begushiybashkir/bbvue/src/views/Profile.vue
avatar fix
This commit is contained in:
2025-10-13 04:43:38 +05:00
parent 988c4d428d
commit 61e8415f8b
3 changed files with 186 additions and 8 deletions
@@ -1,4 +1,47 @@
<!-- AvatarUpload.vue -->
<template>
<div class="avatar-upload">
<div class="avatar-preview">
<img
v-if="previewUrl"
:src="previewUrl"
alt="Аватар"
class="avatar-image"
@error="handleImageError"
/>
<div v-else class="avatar-placeholder">
👤
</div>
</div>
<div v-if="showActions" class="avatar-actions">
<label class="btn btn-small" :class="{ 'btn-disabled': uploading }">
{{ uploading ? 'Загрузка...' : '📷 Загрузить' }}
<input
type="file"
accept="image/*"
@change="handleFileSelect"
:disabled="uploading"
style="display: none;"
>
</label>
<button
v-if="previewUrl"
class="btn btn-small btn-danger"
@click="deleteAvatar"
:disabled="uploading"
>
🗑 Удалить
</button>
</div>
<div v-if="error" class="error-message">
{{ error }}
</div>
</div>
</template>
<script>
import { useAuthStore } from '../stores/auth'
@@ -63,6 +106,40 @@ export default {
return fullUrl
},
handleImageError(event) {
console.error('Error loading avatar image:', event)
this.previewUrl = null
},
handleFileSelect(event) {
const file = event.target.files[0]
if (file) {
// Валидация файла
if (!file.type.startsWith('image/')) {
this.error = 'Пожалуйста, выберите файл изображения'
return
}
if (file.size > 5 * 1024 * 1024) { // 5MB
this.error = 'Размер файла не должен превышать 5MB'
return
}
// Создаем preview
const reader = new FileReader()
reader.onload = (e) => {
this.previewUrl = e.target.result
}
reader.readAsDataURL(file)
// Загружаем на сервер
this.uploadAvatar(file)
// Сбрасываем input
event.target.value = ''
}
},
async uploadAvatar(file) {
this.uploading = true
this.error = ''
@@ -73,10 +150,6 @@ export default {
if (result.success) {
this.$emit('avatar-updated', result.avatar)
// Обновляем preview из обновленного user data
if (this.authStore.user?.avatar) {
this.previewUrl = this.getFullAvatarUrl(this.authStore.user.avatar)
}
} else {
this.error = result.error || 'Ошибка загрузки'
// Восстанавливаем старый preview
@@ -84,7 +157,7 @@ export default {
}
} catch (err) {
console.error('Upload error:', err)
this.error = 'Ошибка загрузки: ' + err.message
this.error = 'Ошибка загрузки: ' + (err.message || 'Неизвестная ошибка')
this.previewUrl = this.getFullAvatarUrl(this.user?.avatar)
} finally {
this.uploading = false
@@ -101,13 +174,13 @@ export default {
if (result.success) {
this.previewUrl = null
this.$emit('avatar-deleted')
this.$emit('avatar-updated', null)
} else {
this.error = result.error || 'Ошибка удаления'
}
} catch (err) {
console.error('Delete error:', err)
this.error = 'Ошибка удаления: ' + err.message
this.error = 'Ошибка удаления: ' + (err.message || 'Неизвестная ошибка')
} finally {
this.uploading = false
}
@@ -115,3 +188,68 @@ export default {
}
}
</script>
<style scoped>
.avatar-upload {
text-align: center;
margin: 1rem 0;
}
.avatar-preview {
width: 120px;
height: 120px;
margin: 0 auto 1rem;
border-radius: 50%;
overflow: hidden;
border: 3px solid #e0e0e0;
background-color: #f5f5f5;
}
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
background-color: #e0e0e0;
}
.avatar-actions {
display: flex;
gap: 0.5rem;
justify-content: center;
flex-wrap: wrap;
}
.btn-small {
padding: 0.5rem 1rem;
font-size: 0.9rem;
}
.btn-danger {
background-color: #dc3545;
color: white;
}
.btn-danger:hover:not(:disabled) {
background-color: #c82333;
}
.btn-disabled {
background-color: #ccc;
cursor: not-allowed;
}
.error-message {
color: #dc3545;
font-size: 0.9rem;
margin-top: 0.5rem;
}
</style>
+9
View File
@@ -113,12 +113,17 @@ export const useAuthStore = defineStore('auth', () => {
formData.append('avatar', avatarFile)
try {
loading.value = true
error.value = ''
const response = await apiClient.post('/user/avatar/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
console.log('Avatar upload response:', response.data)
// Универсальная обработка ответа
let result
if (response.data.success !== undefined) {
@@ -135,9 +140,13 @@ export const useAuthStore = defineStore('auth', () => {
} else {
return { success: false, error: result.error || result.message }
}
} catch (error) {
console.error('Avatar upload error:', error)
const result = handleApiError(error)
return result
} finally {
loading.value = false
}
}
@@ -237,6 +237,37 @@ export default {
<style scoped>
/* Существующие стили остаются */
.avatar-section {
text-align: center;
margin-bottom: 2rem;
}
.avatar-preview {
width: 150px;
height: 150px;
margin: 0 auto 1rem;
border-radius: 50%;
overflow: hidden;
border: 4px solid #2e8b57;
background-color: #f5f5f5;
}
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 4rem;
background-color: #e0e0e0;
}
.stats-header {
display: flex;
justify-content: space-between;