Skip to content

Image Polling + Draft Redirect

Проблема

  1. Нет polling статуса image — после upload бэкенд возвращает status: 'processing', фронтенд никогда не проверяет когда станет ready. UI показывает вечный спиннер.
  2. Потеря контекста черновикаensureDraftForUpload() создаёт product, но URL остаётся /cabinet/products/new. После reload — новая форма, черновик потерян.

Решение

1. Redirect после создания черновика

Правило: любой успешный saveDraft() в mode === 'create', который создал product → redirect на /cabinet/products/{id}/edit с replace: true.

Передача состояния через Pinia (без мерцания):

Новый store app/stores/draftTransfer.ts:

ts
state: {
  formSnapshot: ProductForm | null  // состояние формы
  images: ProductImage[]            // уже загруженные изображения
  pendingFiles: File[]              // файлы, ожидающие upload
  productId: number | null
}
actions: {
  save(form, images, pendingFiles, productId)
  take() → { formSnapshot, images, pendingFiles, productId } | null  // возвращает и очищает
}

Поток (кнопка "Сохранить черновик" на /new):

  1. saveDraft() → POST → получен productId
  2. draftTransferStore.save(form, images, [], productId)
  3. navigateTo('/cabinet/products/{id}/edit', { replace: true })
  4. Edit page mount → draftTransferStore.take()
  5. Данные есть → гидрирует форму из snapshot мгновенно, пропускает loadProduct()

Поток (upload фото на /new):

  1. onAddFiles() → файлы выбраны
  2. ensureDraftForUpload()saveDraft()productId получен
  3. draftTransferStore.save(form, images, files, productId)
  4. Redirect на edit
  5. Edit page mount → take() → гидрирует форму + вызывает addFiles(pendingFiles)

2. Polling статуса изображений

Где: useImageUpload.ts

Триггер: после uploadFile() вернул image с status === 'processing'

Механизм:

  • setInterval(2500) — поллим GET /vendor/products/{productId}
  • Из ответа берём images[], обновляем только те, у которых status изменился
  • Когда все images ready или failedclearInterval

Защита:

  • onUnmounted() → clearInterval
  • Максимум 24 попытки (60 сек) → таймаут → ставим status: 'error' с сообщением
  • Один активный polling на весь composable

Контекст бэкенда:

  • Обработка асинхронная через Redis queue (worker app:process-images)
  • Pipeline: download original → GD resize (3 размера) → watermark (кроме thumbnail) → 2 формата (WebP + JPEG) → upload в public S3
  • Обработка занимает 1-5 секунд
  • Retry на бэке: 3 попытки с exponential backoff (1s → 4s → 16s)
  • ready → все 6 URL заполнены одновременно
  • failederror_message заполнен
  • Нет отдельного endpoint для статуса — только GET /vendor/products/

Файлы

Создать:

ФайлНазначение
app/stores/draftTransfer.tsPinia store для передачи состояния при redirect

Изменить:

ФайлЧто меняется
useImageUpload.tsPolling после upload при status === 'processing'
useProductForm.tssaveDraft() в create mode — signal для redirect
ProductFormPage.vueRedirect после save в create + гидрация из Pinia на mount в edit + upload pending files
product.schema.tsПроверить imageStatusSchema — 'error' vs 'failed'

Не меняется: new.vue, edit.vue, backend, routing.

Порядок реализации

  1. Проверить/исправить imageStatusSchema (error vs failed)
  2. Создать useDraftTransferStore
  3. Добавить polling в useImageUpload
  4. Добавить redirect + гидрацию в ProductFormPage
  5. Тесты