add image upload functionality with history tracking, implement processing job, and integrate Tailwind CSS
parent
35d3d609a6
commit
2225751bc8
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\ProcessHistories;
|
||||||
|
|
||||||
|
class HistoryController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$histories = ProcessHistories::orderBy('id','desc')->get();
|
||||||
|
return view('history.index', compact('histories'));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithFileUploads;
|
||||||
|
use App\Models\ProcessHistories;
|
||||||
|
use Illuminate\Support\Facades\Bus;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use App\Jobs\ProcessImagesJob;
|
||||||
|
|
||||||
|
class ImageUpload extends Component
|
||||||
|
{
|
||||||
|
use WithFileUploads;
|
||||||
|
|
||||||
|
public $images = []; // Массив из 3 элементов
|
||||||
|
public $imagePreviews = [];
|
||||||
|
public $historyId;
|
||||||
|
public $status = 'idle';
|
||||||
|
// Возможные статусы: 'idle', 'uploaded', 'processing', 'done', ...
|
||||||
|
public $progressMessage = '';
|
||||||
|
public $queueJobId;
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'images.*' => 'image|max:4096', // 4MB, можно изменить по желанию
|
||||||
|
];
|
||||||
|
|
||||||
|
public function updatedImages($value, $key)
|
||||||
|
{
|
||||||
|
// После обновления файла, генерируем превью
|
||||||
|
$this->validateOnly('images.*');
|
||||||
|
|
||||||
|
if ($this->images[$key]) {
|
||||||
|
$this->imagePreviews[$key] = $this->images[$key]->temporaryUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function startProcessing()
|
||||||
|
{
|
||||||
|
$this->validate();
|
||||||
|
|
||||||
|
// Сохраняем изображения
|
||||||
|
$savedImages = [];
|
||||||
|
foreach ($this->images as $uploaded) {
|
||||||
|
$path = $uploaded->store('images', 'public');
|
||||||
|
$savedImages[] = [
|
||||||
|
'path' => $path,
|
||||||
|
'original_name' => $uploaded->getClientOriginalName()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаём запись в истории
|
||||||
|
$history = ProcessHistories::create([
|
||||||
|
'images' => $savedImages,
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->historyId = $history->id;
|
||||||
|
$this->status = 'processing';
|
||||||
|
$this->progressMessage = 'Ставим в очередь на обработку...';
|
||||||
|
|
||||||
|
// Отправляем задачу в очередь
|
||||||
|
$job = new ProcessImagesJob($history->id);
|
||||||
|
$dispatch = dispatch($job);
|
||||||
|
|
||||||
|
// Можно при желании сохранить ID задачи, если нужно
|
||||||
|
// $this->queueJobId = ... (в зависимости от драйвера очереди)
|
||||||
|
|
||||||
|
// Дальше фронт будет периодически обновлять состояние либо
|
||||||
|
// мы можем слушать события Livewire (Broadcasting) по готовности
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pollStatus()
|
||||||
|
{
|
||||||
|
if ($this->historyId) {
|
||||||
|
$history = ProcessHistories::find($this->historyId);
|
||||||
|
if ($history) {
|
||||||
|
if ($history->finished_at) {
|
||||||
|
$this->status = 'done';
|
||||||
|
$this->progressMessage = 'Обработка завершена. Файл: ' . $history->step_file_name;
|
||||||
|
} else {
|
||||||
|
// Можно отобразить прогресс, если в историях где-то хранится этап
|
||||||
|
$count = $history->processed_count;
|
||||||
|
$this->progressMessage = "Обработка в процессе... {$count}/3 изображений";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.image-upload');
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"/js/app.js": "/js/app.js",
|
||||||
|
"/css/app.css": "/css/app.css"
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
<x-app-layout>
|
||||||
|
<x-slot name="header">
|
||||||
|
История обработок
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
<div class="p-4">
|
||||||
|
<h1 class="text-2xl font-bold mb-4">История обработок</h1>
|
||||||
|
<table class="min-w-full border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr class="border-b">
|
||||||
|
<th class="p-2 text-left">ID</th>
|
||||||
|
<th class="p-2 text-left">Изображения</th>
|
||||||
|
<th class="p-2 text-left">Step файл</th>
|
||||||
|
<th class="p-2 text-left">Время начала</th>
|
||||||
|
<th class="p-2 text-left">Время окончания</th>
|
||||||
|
<th class="p-2 text-left">Обработано</th>
|
||||||
|
<th class="p-2 text-left">Затраты времени (сек)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($histories as $h)
|
||||||
|
<tr class="border-b">
|
||||||
|
<td class="p-2">{{ $h->id }}</td>
|
||||||
|
<td class="p-2">
|
||||||
|
@foreach($h->images as $img)
|
||||||
|
<div>{{ $img['original_name'] }} ({{ $img['path'] }})</div>
|
||||||
|
@endforeach
|
||||||
|
</td>
|
||||||
|
<td class="p-2">{{ $h->step_file_name }}</td>
|
||||||
|
<td class="p-2">{{ $h->started_at }}</td>
|
||||||
|
<td class="p-2">{{ $h->finished_at }}</td>
|
||||||
|
<td class="p-2">{{ $h->processed_count }}/3</td>
|
||||||
|
<td class="p-2">
|
||||||
|
@if($h->stages_timing)
|
||||||
|
Очередь: {{ $h->stages_timing['queue_wait_time'] ?? 'N/A' }}<br>
|
||||||
|
Загрузка: {{ $h->stages_timing['upload_time'] ?? 'N/A' }}<br>
|
||||||
|
Обработка: {{ $h->stages_timing['processing_time'] ?? 'N/A' }}<br>
|
||||||
|
Сохранение: {{ $h->stages_timing['saving_time'] ?? 'N/A' }}<br>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</x-app-layout>
|
@ -0,0 +1,36 @@
|
|||||||
|
<div class="p-4 space-y-4">
|
||||||
|
<h1 class="text-2xl font-bold">Загрузка изображений</h1>
|
||||||
|
<div class="flex space-x-4">
|
||||||
|
@for($i=0; $i<3; $i++)
|
||||||
|
<div class="border-2 border-dashed border-gray-300 w-32 h-32 flex items-center justify-center">
|
||||||
|
@if(isset($imagePreviews[$i]))
|
||||||
|
<img src="{{ $imagePreviews[$i] }}" class="object-cover w-full h-full" alt="Preview">
|
||||||
|
@else
|
||||||
|
<label class="cursor-pointer text-center">
|
||||||
|
<span class="text-gray-500">Перетащите файл сюда или кликните</span>
|
||||||
|
<input type="file" class="hidden" wire:model="images.{{ $i }}">
|
||||||
|
</label>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endfor
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-x-2">
|
||||||
|
<button wire:click="startProcessing"
|
||||||
|
class="bg-blue-500 text-white px-4 py-2 rounded"
|
||||||
|
@disabled($status !== 'idle' && $status !== 'uploaded')>
|
||||||
|
Начать
|
||||||
|
</button>
|
||||||
|
@if($status === 'processing')
|
||||||
|
<button wire:poll.2s="pollStatus" class="hidden"></button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($progressMessage)
|
||||||
|
<div class="text-gray-700 mt-4">{{ $progressMessage }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($status === 'done')
|
||||||
|
<a href="{{ route('history') }}" class="text-blue-500 underline">Посмотреть историю обработок</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
Loading…
Reference in new issue