feat(banner): add banner feature

This commit is contained in:
2025-10-25 19:55:01 +03:00
parent c3c0d6d2c1
commit 05e7cafd0f
11 changed files with 156 additions and 3 deletions

View File

@@ -30,6 +30,7 @@ if (is_readable($sysLibPath . '/oc_telegram_shop.phar')) {
* @property User $user * @property User $user
* @property ModelCustomerCustomerGroup $model_customer_customer_group * @property ModelCustomerCustomerGroup $model_customer_customer_group
* @property ModelLocalisationOrderStatus $model_localisation_order_status * @property ModelLocalisationOrderStatus $model_localisation_order_status
* @property ModelDesignBanner $model_design_banner
* @property DB $db * @property DB $db
* @property Log $log * @property Log $log
*/ */
@@ -350,6 +351,7 @@ TEXT,
'module_tgshop_enable_store' => 1, 'module_tgshop_enable_store' => 1,
'module_tgshop_feature_coupons' => 0, 'module_tgshop_feature_coupons' => 0,
'module_tgshop_feature_vouchers' => 0, 'module_tgshop_feature_vouchers' => 0,
'module_tgshop_home_banner_id' => null,
]; ];
} }
@@ -365,6 +367,11 @@ TEXT,
'user_token=' . $this->session->data['user_token'], 'user_token=' . $this->session->data['user_token'],
true true
); );
$ocBannersLink = $this->url->link(
'design/banner',
'user_token=' . $this->session->data['user_token'],
true
);
return [ return [
'general' => [ 'general' => [
@@ -517,6 +524,14 @@ HTML,
], ],
'help' => <<<HTML 'help' => <<<HTML
Позволяет покупателям использовать <a href="{$ocVouchersLink}" target="_blank">подарочные сертификаты OpenCart</a> при оформлении заказа. Позволяет покупателям использовать <a href="{$ocVouchersLink}" target="_blank">подарочные сертификаты OpenCart</a> при оформлении заказа.
HTML,
],
'module_tgshop_home_banner_id' => [
'type' => 'select',
'options' => $this->getBannersList(),
'help' => <<<HTML
<a href="{$ocBannersLink}" target="_blank">Стандартный OpenCart баннер</a> отображаемый на главной странице магазина. Рекомендуемая максимальная высота изображения для баннера - 200 пикселей.
HTML, HTML,
], ],
], ],
@@ -645,4 +660,16 @@ HTML,
$this->log->write('[TELECART] Ошибка удаления старых assets: ' . $e->getMessage()); $this->log->write('[TELECART] Ошибка удаления старых assets: ' . $e->getMessage());
} }
} }
private function getBannersList(): array
{
$this->load->model('design/banner');
$allBanners = $this->model_design_banner->getBanners();
$map = [];
foreach ($allBanners as $item) {
$map[(int) $item['banner_id']] = $item['name'];
}
return [null => 'Не показывать'] + $map;
}
} }

View File

@@ -32,6 +32,7 @@ $_['lbl_module_tgshop_featured_categories'] = 'Избранные категор
$_['lbl_module_tgshop_enable_store'] = 'Разрешить покупки'; $_['lbl_module_tgshop_enable_store'] = 'Разрешить покупки';
$_['lbl_module_tgshop_feature_coupons'] = 'Промокоды'; $_['lbl_module_tgshop_feature_coupons'] = 'Промокоды';
$_['lbl_module_tgshop_feature_vouchers'] = 'Подарочные сертификаты'; $_['lbl_module_tgshop_feature_vouchers'] = 'Подарочные сертификаты';
$_['lbl_module_tgshop_home_banner_id'] = 'Баннер на главной';
// Entry // Entry
$_['entry_status'] = 'Статус'; $_['entry_status'] = 'Статус';

View File

@@ -91,6 +91,7 @@ class ControllerExtensionTgshopHandle extends Controller
$this->config->get('module_tgshop_feature_vouchers'), $this->config->get('module_tgshop_feature_vouchers'),
FILTER_VALIDATE_BOOLEAN FILTER_VALIDATE_BOOLEAN
), ),
'home_banner_id' => $this->config->get('module_tgshop_home_banner_id'),
]); ]);
$app->bind(OcModelCatalogProductAdapter::class, function () { $app->bind(OcModelCatalogProductAdapter::class, function () {

View File

@@ -3,6 +3,7 @@
namespace Openguru\OpenCartFramework\ImageTool; namespace Openguru\OpenCartFramework\ImageTool;
use Intervention\Image\ImageManager; use Intervention\Image\ImageManager;
use InvalidArgumentException;
class ImageTool implements ImageToolInterface class ImageTool implements ImageToolInterface
{ {
@@ -19,10 +20,14 @@ class ImageTool implements ImageToolInterface
$this->manager = new ImageManager(['driver' => $driver]); $this->manager = new ImageManager(['driver' => $driver]);
} }
public function resize(string $path, int $width, int $height, ?string $default = null, string $format = 'webp'): ?string public function resize(string $path, ?int $width = null, ?int $height = null, ?string $default = null, string $format = 'webp'): ?string
{ {
$filename = is_file($this->imageDir . $path) ? $path : $default; $filename = is_file($this->imageDir . $path) ? $path : $default;
if (! $width && ! $height) {
throw new InvalidArgumentException('Width or height must be set');
}
if (! $filename || ! is_file($this->imageDir . $filename)) { if (! $filename || ! is_file($this->imageDir . $filename)) {
return null; return null;
} }

View File

@@ -9,8 +9,8 @@ interface ImageToolInterface
public function resize( public function resize(
string $path, string $path,
int $width, ?int $width = null,
int $height, ?int $height = null,
?string $default = null, ?string $default = null,
string $format = 'webp' string $format = 'webp'
): ?string; ): ?string;

View File

@@ -8,6 +8,7 @@ use Config;
use Language; use Language;
use Loader; use Loader;
use ModelCatalogProduct; use ModelCatalogProduct;
use ModelDesignBanner;
use ModelSettingSetting; use ModelSettingSetting;
use ModelToolImage; use ModelToolImage;
use Registry; use Registry;
@@ -25,6 +26,7 @@ use Url;
* @property ModelToolImage $model_tool_image * @property ModelToolImage $model_tool_image
* @property ModelCatalogProduct $model_catalog_product * @property ModelCatalogProduct $model_catalog_product
* @property ModelSettingSetting $model_setting_setting * @property ModelSettingSetting $model_setting_setting
* @property ModelDesignBanner $model_design_banner
*/ */
class OcRegistryDecorator class OcRegistryDecorator
{ {

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Handlers;
use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
class BannerHandler
{
private OcRegistryDecorator $registry;
private ImageToolInterface $imageTool;
private Settings $settings;
public function __construct(OcRegistryDecorator $registry, ImageToolInterface $imageTool, Settings $settings)
{
$this->registry = $registry;
$this->imageTool = $imageTool;
$this->settings = $settings;
$this->registry->load->model('design/banner');
}
public function show(): JsonResponse
{
$data = [];
$bannerId = $this->settings->get('home_banner_id');
if ($bannerId) {
$banner = $this->registry->model_design_banner->getBanner($bannerId);
foreach ($banner as $index => $result) {
if (is_file(DIR_IMAGE . $result['image'])) {
$data[] = [
'id' => $index,
'title' => $result['title'],
'link' => rtrim($this->settings->get('base_url'), '/') . '/' . $result['link'],
'image' => $this->imageTool->resize($result['image'], null, 200),
];
}
}
}
return new JsonResponse([
'data' => $data,
]);
}
}

View File

@@ -1,5 +1,6 @@
<?php <?php
use App\Handlers\BannerHandler;
use App\Handlers\CartHandler; use App\Handlers\CartHandler;
use App\Handlers\CategoriesHandler; use App\Handlers\CategoriesHandler;
use App\Handlers\FiltersHandler; use App\Handlers\FiltersHandler;
@@ -26,5 +27,7 @@ return [
'manifest' => [SettingsHandler::class, 'manifest'], 'manifest' => [SettingsHandler::class, 'manifest'],
'testTgMessage' => [SettingsHandler::class, 'testTgMessage'], 'testTgMessage' => [SettingsHandler::class, 'testTgMessage'],
'banner' => [BannerHandler::class, 'show'],
'webhook' => [TelegramHandler::class, 'webhook'], 'webhook' => [TelegramHandler::class, 'webhook'],
]; ];

View File

@@ -0,0 +1,60 @@
<template>
<div v-if="slides.length > 0" class="app-banner px-4">
<Swiper
class="select-none"
:slides-per-view="1"
:space-between="50"
pagination
:pagination="{ clickable: true }"
@swiper="onSwiper"
@slideChange="onSlideChange"
>
<SwiperSlide v-for="slide in slides" :key="slide.id">
<img :src="slide.image" :alt="slide.title">
</SwiperSlide>
</Swiper>
</div>
</template>
<script setup>
import {Swiper, SwiperSlide} from 'swiper/vue';
import 'swiper/css';
import 'swiper/css/navigation';
import {onMounted, ref} from "vue";
import {fetchBanner} from "@/utils/ftch.js";
const slides = ref([]);
const onSwiper = (swiper) => {
console.log(swiper);
};
const onSlideChange = () => {
console.log('slide change');
};
onMounted(async () => {
const response = await fetchBanner();
slides.value = response.data;
})
</script>
<style>
.app-banner {
overflow: hidden;
}
.app-banner .swiper-horizontal > .swiper-pagination-bullets {
position: relative;
bottom: 0;
}
.app-banner .swiper-horizontal .swiper-slide {
display: flex;
align-items: center;
justify-content: center;
}
.app-banner .swiper-horizontal .swiper-slide > img {
border-radius: var(--radius-box);
}
</style>

View File

@@ -92,4 +92,8 @@ export async function setVoucher(voucher) {
}); });
} }
export async function fetchBanner() {
return await ftch('banner');
}
export default ftch; export default ftch;

View File

@@ -2,6 +2,7 @@
<div ref="goodsRef" class="pb-20"> <div ref="goodsRef" class="pb-20">
<CategoriesInline/> <CategoriesInline/>
<Banner/>
<div class="px-5 fixed z-50 w-full opacity-90" style="bottom: calc(var(--tg-safe-area-inset-bottom) + 80px);"> <div class="px-5 fixed z-50 w-full opacity-90" style="bottom: calc(var(--tg-safe-area-inset-bottom) + 80px);">
<div class="flex justify-center"> <div class="flex justify-center">
@@ -35,6 +36,7 @@ import IconFunnel from "@/components/Icons/IconFunnel.vue";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
import ftch from "@/utils/ftch.js"; import ftch from "@/utils/ftch.js";
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js"; import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
import Banner from "@/components/Banner.vue";
defineOptions({ defineOptions({
name: 'Home' name: 'Home'