feat(banner): add banner feature
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'] = 'Статус';
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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'],
|
||||
];
|
||||
|
||||
60
spa/src/components/Banner.vue
Normal file
60
spa/src/components/Banner.vue
Normal 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>
|
||||
@@ -92,4 +92,8 @@ export async function setVoucher(voucher) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchBanner() {
|
||||
return await ftch('banner');
|
||||
}
|
||||
|
||||
export default ftch;
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user