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 ModelCustomerCustomerGroup $model_customer_customer_group
* @property ModelLocalisationOrderStatus $model_localisation_order_status
* @property ModelDesignBanner $model_design_banner
* @property DB $db
* @property Log $log
*/
@@ -350,6 +351,7 @@ TEXT,
'module_tgshop_enable_store' => 1,
'module_tgshop_feature_coupons' => 0,
'module_tgshop_feature_vouchers' => 0,
'module_tgshop_home_banner_id' => null,
];
}
@@ -365,6 +367,11 @@ TEXT,
'user_token=' . $this->session->data['user_token'],
true
);
$ocBannersLink = $this->url->link(
'design/banner',
'user_token=' . $this->session->data['user_token'],
true
);
return [
'general' => [
@@ -517,6 +524,14 @@ HTML,
],
'help' => <<<HTML
Позволяет покупателям использовать <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,
],
],
@@ -645,4 +660,16 @@ HTML,
$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_feature_coupons'] = 'Промокоды';
$_['lbl_module_tgshop_feature_vouchers'] = 'Подарочные сертификаты';
$_['lbl_module_tgshop_home_banner_id'] = 'Баннер на главной';
// Entry
$_['entry_status'] = 'Статус';

View File

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

View File

@@ -3,6 +3,7 @@
namespace Openguru\OpenCartFramework\ImageTool;
use Intervention\Image\ImageManager;
use InvalidArgumentException;
class ImageTool implements ImageToolInterface
{
@@ -19,10 +20,14 @@ class ImageTool implements ImageToolInterface
$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;
if (! $width && ! $height) {
throw new InvalidArgumentException('Width or height must be set');
}
if (! $filename || ! is_file($this->imageDir . $filename)) {
return null;
}

View File

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

View File

@@ -8,6 +8,7 @@ use Config;
use Language;
use Loader;
use ModelCatalogProduct;
use ModelDesignBanner;
use ModelSettingSetting;
use ModelToolImage;
use Registry;
@@ -25,6 +26,7 @@ use Url;
* @property ModelToolImage $model_tool_image
* @property ModelCatalogProduct $model_catalog_product
* @property ModelSettingSetting $model_setting_setting
* @property ModelDesignBanner $model_design_banner
*/
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
use App\Handlers\BannerHandler;
use App\Handlers\CartHandler;
use App\Handlers\CategoriesHandler;
use App\Handlers\FiltersHandler;
@@ -26,5 +27,7 @@ return [
'manifest' => [SettingsHandler::class, 'manifest'],
'testTgMessage' => [SettingsHandler::class, 'testTgMessage'],
'banner' => [BannerHandler::class, 'show'],
'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;

View File

@@ -2,6 +2,7 @@
<div ref="goodsRef" class="pb-20">
<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="flex justify-center">
@@ -35,6 +36,7 @@ import IconFunnel from "@/components/Icons/IconFunnel.vue";
import {useRouter} from "vue-router";
import ftch from "@/utils/ftch.js";
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
import Banner from "@/components/Banner.vue";
defineOptions({
name: 'Home'