fix: resolve image upload issues in posts
- Dashboard: disable submit button during uploads, show upload errors, guard against pending uploads - Filament: add maxParallelUploads(1) to fix race condition (filamentphp#13306) - Filament: add image/webp to gallery acceptedFileTypes, increase resize 30->50 - Dashboard: add image/webp to gallery input accept - Add missing notifications table migration for Filament databaseNotifications
This commit is contained in:
@@ -160,7 +160,7 @@ class PostForm
|
||||
->image()
|
||||
->directory('posts/gallery')
|
||||
->optimize('jpg')
|
||||
->resize(30)
|
||||
->resize(50)
|
||||
->imageEditor()
|
||||
->multiple()
|
||||
->reorderable()
|
||||
@@ -168,8 +168,9 @@ class PostForm
|
||||
->helperText('Загрузите дополнительные изображения для галереи')
|
||||
->maxFiles(200)
|
||||
->maxSize(20480)
|
||||
->acceptedFileTypes(['image/jpeg', 'image/png'])
|
||||
->imagePreviewHeight('150'),
|
||||
->acceptedFileTypes(['image/jpeg', 'image/png', 'image/webp'])
|
||||
->imagePreviewHeight('150')
|
||||
->maxParallelUploads(1),
|
||||
]),
|
||||
Tabs\Tab::make('Слайдер')
|
||||
->icon('heroicon-o-view-columns')
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('notifications', function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
$table->string('type');
|
||||
$table->morphs('notifiable');
|
||||
$table->text('data');
|
||||
$table->timestamp('read_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('notifications');
|
||||
}
|
||||
};
|
||||
@@ -26,15 +26,15 @@
|
||||
<button
|
||||
type="button"
|
||||
@click="submitForm"
|
||||
:disabled="form.processing"
|
||||
:disabled="form.processing || uploadingImages"
|
||||
class="inline-flex items-center gap-2 px-5 py-2 bg-primary text-white text-sm font-medium rounded-lg hover:bg-primary-hover transition-all disabled:opacity-50 disabled:cursor-not-allowed shadow-sm hover:shadow-md"
|
||||
>
|
||||
<svg v-if="form.processing" class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24">
|
||||
<svg v-if="form.processing || uploadingImages" class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<DashboardIcon v-else name="check" size="4" />
|
||||
{{ isEdit ? 'Сохранить' : 'Создать' }}
|
||||
{{ uploadStatusText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,6 +76,28 @@
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- Upload Error -->
|
||||
<transition
|
||||
enter-active-class="transition duration-300 ease-out"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition duration-200 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 -translate-y-2"
|
||||
>
|
||||
<div v-if="uploadError" class="mb-4 p-4 bg-amber-500/10 border border-amber-500/20 rounded-lg">
|
||||
<div class="flex items-start gap-3">
|
||||
<DashboardIcon name="exclamation-triangle" size="5" class="text-amber-600 flex-shrink-0 mt-0.5" />
|
||||
<div class="flex-1">
|
||||
<span class="text-sm text-foreground font-medium">{{ uploadError }}</span>
|
||||
</div>
|
||||
<button @click="uploadError = null" class="text-muted-foreground-1 hover:text-foreground">
|
||||
<DashboardIcon name="x-mark" size="4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<form @submit.prevent="submitForm" class="space-y-6">
|
||||
<!-- Tabs Navigation -->
|
||||
<div class="bg-layer border border-layer-line rounded-lg shadow-xs overflow-hidden">
|
||||
@@ -497,7 +519,7 @@
|
||||
ref="galleryInput"
|
||||
type="file"
|
||||
class="hidden"
|
||||
accept="image/jpeg,image/png"
|
||||
accept="image/jpeg,image/png,image/webp"
|
||||
multiple
|
||||
@change="handleGalleryFileSelect"
|
||||
/>
|
||||
@@ -596,6 +618,7 @@ export default {
|
||||
isDraggingPreview: false,
|
||||
isDraggingGallery: false,
|
||||
uploadingImages: false,
|
||||
uploadError: null,
|
||||
newTag: '',
|
||||
newAuthor: '',
|
||||
PostStatus,
|
||||
@@ -698,6 +721,11 @@ export default {
|
||||
return null;
|
||||
}).filter(url => url !== null);
|
||||
},
|
||||
|
||||
uploadStatusText() {
|
||||
if (this.uploadingImages) return 'Загрузка файлов...';
|
||||
return this.isEdit ? 'Сохранить' : 'Создать';
|
||||
},
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
@@ -812,6 +840,7 @@ export default {
|
||||
if (!file) return;
|
||||
|
||||
this.uploadingImages = true;
|
||||
this.uploadError = null;
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
@@ -830,9 +859,12 @@ export default {
|
||||
|
||||
if (result.success && result.paths && result.paths.length > 0) {
|
||||
this.form.preview = result.paths[0];
|
||||
} else {
|
||||
this.uploadError = result.error || 'Не удалось загрузить главное изображение';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Preview upload error:', error);
|
||||
this.uploadError = 'Ошибка сети при загрузке главного изображения. Проверьте подключение.';
|
||||
} finally {
|
||||
this.uploadingImages = false;
|
||||
}
|
||||
@@ -858,6 +890,7 @@ export default {
|
||||
if (files.length === 0) return;
|
||||
|
||||
this.uploadingImages = true;
|
||||
this.uploadError = null;
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
@@ -880,9 +913,12 @@ export default {
|
||||
result.paths.forEach(path => {
|
||||
this.form.images.push(path);
|
||||
});
|
||||
} else {
|
||||
this.uploadError = result.error || 'Не удалось загрузить изображения галереи';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Image upload error:', error);
|
||||
this.uploadError = 'Ошибка сети при загрузке изображений. Проверьте подключение.';
|
||||
} finally {
|
||||
this.uploadingImages = false;
|
||||
}
|
||||
@@ -951,8 +987,14 @@ export default {
|
||||
},
|
||||
|
||||
submitForm() {
|
||||
if (this.uploadingImages) {
|
||||
this.uploadError = 'Дождитесь завершения загрузки изображений перед сохранением.';
|
||||
return;
|
||||
}
|
||||
|
||||
this.form.processing = true;
|
||||
this.form.errors = {};
|
||||
this.uploadError = null;
|
||||
|
||||
// Фильтруем изображения - оставляем только строки (пути)
|
||||
this.form.images = this.form.images.filter(img => typeof img === 'string' && img.length > 0);
|
||||
|
||||
Reference in New Issue
Block a user