доставка в плавающем

This commit is contained in:
Alisa
2026-06-08 10:58:47 +03:00
parent 3a065b34d6
commit 256afcbb80
18 changed files with 1478 additions and 187 deletions
+50
View File
@@ -45,6 +45,56 @@ final class CatalogController
]);
}
public function promo(): void
{
$categoryRepository = new CategoryRepository();
$productRepository = new ProductRepository();
$pagination = $this->paginationInput();
$listing = $productRepository->promoListing($pagination['limit'], $pagination['offset']);
view('catalog/index', [
'title' => 'Акции Рыбсток - продукты по стоковым ценам с доставкой',
'metaDescription' => 'Акционные товары Рыбсток: рыба, морепродукты, мясо, сыры, икра, полуфабрикаты и бакалея по стоковым ценам с доставкой по Москве и Московской области.',
'metaKeywords' => 'акции Рыбсток, скидки на рыбу, продукты по акции Москва, рыба морепродукты мясо сыры по стоковым ценам',
'breadcrumbs' => [
['title' => 'Главная', 'url' => '/'],
['title' => 'Акции'],
],
'categories' => $categoryRepository->tree(),
'currentCategory' => null,
'catalogHeading' => 'Акции',
'catalogEyebrow' => 'Подборка',
'catalogLead' => 'Товары с акционной витриной и перечеркнутой ценой. Итоговая цена в корзине дополнительно зависит от выбранного способа оплаты.',
'products' => $listing['products'],
'pagination' => $this->paginationData('/promo', $listing['total'], count($listing['products']), $pagination),
]);
}
public function newest(): void
{
$categoryRepository = new CategoryRepository();
$productRepository = new ProductRepository();
$pagination = $this->paginationInput();
$listing = $productRepository->newListing($pagination['limit'], $pagination['offset']);
view('catalog/index', [
'title' => 'Новинки Рыбсток - последние поступления на склад',
'metaDescription' => 'Новые поступления Рыбсток: свежие позиции каталога, новые фасовки и товары, недавно добавленные на склад с доставкой по Москве и Московской области.',
'metaKeywords' => 'новинки Рыбсток, новые поступления рыбы, свежие поступления морепродуктов, новые товары склад продуктов Москва',
'breadcrumbs' => [
['title' => 'Главная', 'url' => '/'],
['title' => 'Новинки'],
],
'categories' => $categoryRepository->tree(),
'currentCategory' => null,
'catalogHeading' => 'Новинки',
'catalogEyebrow' => 'Последние поступления',
'catalogLead' => 'Последние добавленные позиции и товары, у которых изменилась фасовка. Новинки помогают быстро увидеть, что недавно появилось на складе.',
'products' => $listing['products'],
'pagination' => $this->paginationData('/new', $listing['total'], count($listing['products']), $pagination),
]);
}
public function search(string $query): void
{
$_GET['q'] = trim($query);
+3 -3
View File
@@ -34,15 +34,15 @@ final class HomeController
$homeCatalogSections[] = [
'key' => 'promos',
'title' => 'Акции',
'url' => '/catalog',
'count' => 10,
'url' => '/promo',
'count' => 50,
'kind' => 'promo',
'products' => $productRepository->promoPreview(10),
];
$homeCatalogSections[] = [
'key' => 'new',
'title' => 'Новинки',
'url' => '/catalog',
'url' => '/new',
'count' => 50,
'kind' => 'new',
'products' => $productRepository->newPreview(10),
+68 -3
View File
@@ -30,7 +30,15 @@ final class ProductRepository extends BaseRepository
*/
public function promoPreview(int $limit = 10): array
{
$limit = max(1, min($limit, 10));
return array_slice($this->promoProducts(max(1, min($limit, 10))), 0, max(1, min($limit, 10)));
}
/**
* @return array<int, array<string, mixed>>
*/
private function promoProducts(int $limit = 50): array
{
$limit = max(1, min($limit, 50));
$products = $this->manualPromoPreview($limit);
$usedIds = array_flip(array_map(static fn (array $product): int => (int) ($product['id'] ?? 0), $products));
@@ -63,6 +71,20 @@ final class ProductRepository extends BaseRepository
);
}
/**
* @return array{products: array<int, array<string, mixed>>, total: int}
*/
public function promoListing(int $limit = 48, int $offset = 0): array
{
$products = $this->promoProducts(50);
$total = count($products);
return [
'products' => array_slice($products, max(0, $offset), max(1, min($limit, 48))),
'total' => $total,
];
}
/**
* @return array<int, array<string, mixed>>
*/
@@ -88,6 +110,20 @@ final class ProductRepository extends BaseRepository
return $products;
}
/**
* @return array{products: array<int, array<string, mixed>>, total: int}
*/
public function newListing(int $limit = 48, int $offset = 0): array
{
$products = $this->newPreview(50);
$total = count($products);
return [
'products' => array_slice($products, max(0, $offset), max(1, min($limit, 48))),
'total' => $total,
];
}
/**
* @return array<int, array<string, mixed>>
*/
@@ -151,6 +187,23 @@ final class ProductRepository extends BaseRepository
return (int) $statement->fetchColumn();
}
/**
* @return array<int, array{slug: string, updated_at: string|null, published_at: string|null, created_at: string|null}>
*/
public function sitemapProducts(int $limit = 10000): array
{
$statement = $this->pdo()->query(
'SELECT slug, updated_at, published_at, created_at
FROM products
WHERE is_published = 1
AND is_available = 1
ORDER BY updated_at DESC, published_at DESC, created_at DESC, id DESC
LIMIT ' . max(1, min($limit, 10000))
);
return $statement->fetchAll();
}
/**
* @return array{products: array<int, array<string, mixed>>, total: int}
*/
@@ -291,7 +344,17 @@ final class ProductRepository extends BaseRepository
);
$statement->execute(['product_id' => $productId]);
return $statement->fetchAll();
return array_map(
static function (array $image): array {
$path = (string) ($image['path'] ?? '');
$image['preview_path'] = \function_exists('optimized_image_path')
? \optimized_image_path($path, 'preview')
: $path;
return $image;
},
$statement->fetchAll()
);
}
/**
@@ -435,7 +498,9 @@ final class ProductRepository extends BaseRepository
'variantId' => !empty($row['variant_id']) ? (int) $row['variant_id'] : $variantId,
'name' => (string) ($row['name'] ?? ''),
'slug' => (string) ($row['slug'] ?? ''),
'image' => (string) ($row['main_image_path'] ?? ''),
'image' => \function_exists('optimized_image_path')
? \optimized_image_path((string) ($row['main_image_path'] ?? ''), 'preview')
: (string) ($row['main_image_path'] ?? ''),
'variantName' => (string) ($row['variant_name'] ?? 'Фасовка'),
'unit' => (string) ($row['unit'] ?? ''),
'packageQuantity' => (float) ($row['package_quantity'] ?? 0),
+38
View File
@@ -36,6 +36,44 @@ function versioned_asset(string $path): string
return $assetPath . $querySeparator . filemtime($publicPath);
}
function public_asset_exists(string $path): bool
{
$assetPath = '/' . ltrim(strtok($path, '?') ?: $path, '/');
return is_file(base_path('public' . $assetPath));
}
function optimized_image_path(?string $path, string $mode = 'preview'): string
{
$assetPath = trim((string) $path);
if ($assetPath === '' || !str_starts_with($assetPath, '/assets/uploads/old/products/')) {
return $assetPath;
}
$assetPath = '/' . ltrim(strtok($assetPath, '?') ?: $assetPath, '/');
$directory = rtrim(str_replace('\\', '/', dirname($assetPath)), '/');
$filename = basename($assetPath);
$isFull = str_starts_with($filename, 'full_');
$baseFilename = $isFull ? substr($filename, 5) : $filename;
$candidates = $mode === 'detail'
? [$baseFilename, 'thumb_' . $baseFilename]
: ['thumb_' . $baseFilename, $baseFilename];
foreach ($candidates as $candidate) {
if ($candidate === '' || $candidate === $filename) {
continue;
}
$candidatePath = $directory . '/' . $candidate;
if (public_asset_exists($candidatePath)) {
return $candidatePath;
}
}
return $assetPath;
}
function view(string $template, array $data = []): void
{
View::render($template, $data);