feat: add carousel for images
This commit is contained in:
@@ -11,6 +11,7 @@ use Openguru\OpenCartFramework\Http\JsonResponse;
|
|||||||
use Openguru\OpenCartFramework\Http\Request;
|
use Openguru\OpenCartFramework\Http\Request;
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
|
||||||
class ProductsHandler
|
class ProductsHandler
|
||||||
{
|
{
|
||||||
@@ -31,7 +32,10 @@ class ProductsHandler
|
|||||||
{
|
{
|
||||||
$languageId = 1;
|
$languageId = 1;
|
||||||
$page = $request->get('page', 1);
|
$page = $request->get('page', 1);
|
||||||
$perPage = $request->get('perPage', 20);
|
$perPage = $request->get('perPage', 10);
|
||||||
|
|
||||||
|
$imageWidth = 200;
|
||||||
|
$imageHeight = 200;
|
||||||
|
|
||||||
$products = $this->queryBuilder->newQuery()
|
$products = $this->queryBuilder->newQuery()
|
||||||
->select([
|
->select([
|
||||||
@@ -53,17 +57,42 @@ class ProductsHandler
|
|||||||
->forPage($page, $perPage)
|
->forPage($page, $perPage)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
$productIds = Arr::pluck($products, 'product_id');
|
||||||
|
$productsImages = $this->queryBuilder->newQuery()
|
||||||
|
->select([
|
||||||
|
'products_images.product_id' => 'product_id',
|
||||||
|
'products_images.image' => 'image',
|
||||||
|
])
|
||||||
|
->from(db_table('product_image'), 'products_images')
|
||||||
|
->orderBy('products_images.sort_order', 'ASC')
|
||||||
|
->whereIn('product_id', $productIds)
|
||||||
|
->get();
|
||||||
|
|
||||||
/** @var Closure $resize */
|
/** @var Closure $resize */
|
||||||
$resize = Application::getInstance()->get('image_resize');
|
$resize = Application::getInstance()->get('image_resize');
|
||||||
|
|
||||||
return new JsonResponse([
|
$productsImagesMap = [];
|
||||||
'data' => array_map(function ($product) use ($resize) {
|
foreach ($productsImages as $item) {
|
||||||
if ($product['product_image']) {
|
$productsImagesMap[$item['product_id']][] = [
|
||||||
$image = $resize($product['product_image'], 500, 500);
|
'url' => $resize($item['image'], $imageWidth, $imageHeight),
|
||||||
} else {
|
'alt' => 'Product Image',
|
||||||
$image = $resize('placeholder.png', 500, 500);
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'data' => array_map(function ($product) use ($resize, $productsImagesMap, $imageWidth, $imageHeight) {
|
||||||
|
$allImages = [];
|
||||||
|
if ($product['product_image']) {
|
||||||
|
$image = $resize($product['product_image'], $imageWidth, $imageHeight);
|
||||||
|
} else {
|
||||||
|
$image = $resize('placeholder.png', $imageWidth, $imageHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
$allImages[] = [
|
||||||
|
'url' => $image,
|
||||||
|
'alt' => $product['product_name'],
|
||||||
|
];
|
||||||
|
|
||||||
$price = $this->currency->format(
|
$price = $this->currency->format(
|
||||||
$this->tax->calculate(
|
$this->tax->calculate(
|
||||||
$product['price'],
|
$product['price'],
|
||||||
@@ -73,12 +102,16 @@ class ProductsHandler
|
|||||||
$this->settings->get('oc_currency'),
|
$this->settings->get('oc_currency'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (! empty($productsImagesMap[$product['product_id']])) {
|
||||||
|
$allImages = array_merge($allImages, $productsImagesMap[$product['product_id']]);
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => (int)$product['product_id'],
|
'id' => (int)$product['product_id'],
|
||||||
'product_quantity' => (int)$product['product_quantity'],
|
'product_quantity' => (int)$product['product_quantity'],
|
||||||
'name' => $product['product_name'],
|
'name' => $product['product_name'],
|
||||||
'price' => $price,
|
'price' => $price,
|
||||||
'image' => $image,
|
'images' => $allImages,
|
||||||
];
|
];
|
||||||
}, $products),
|
}, $products),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -4,11 +4,13 @@
|
|||||||
<h2 class="sr-only">Products</h2>
|
<h2 class="sr-only">Products</h2>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
<div class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
||||||
<a v-for="product in products" :key="product.id" class="group"
|
<a v-for="product in products" :key="product.id" class="group">
|
||||||
@touchstart="handleHaptic"
|
<div class="carousel carousel-center rounded-box" ref="carouselRef" @scroll.passive="onScroll">
|
||||||
>
|
<div v-for="(image, i) in product.images" :key="i" class="carousel-item">
|
||||||
<img :src="product.image" :alt="product.name"
|
<img :src="image.url" :alt="image.alt"/>
|
||||||
class="aspect-square w-full rounded-lg bg-gray-200 object-cover group-hover:opacity-75 xl:aspect-7/8"/>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3 class="mt-4 text-sm text-gray-700">{{ product.name }}</h3>
|
<h3 class="mt-4 text-sm text-gray-700">{{ product.name }}</h3>
|
||||||
<p class="mt-1 text-lg font-medium text-gray-900">{{ product.price }}</p>
|
<p class="mt-1 text-lg font-medium text-gray-900">{{ product.price }}</p>
|
||||||
</a>
|
</a>
|
||||||
@@ -24,11 +26,25 @@ import {onMounted, ref} from "vue";
|
|||||||
const products = ref([]);
|
const products = ref([]);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const {data} = await $fetch('https://ocstore.nikitakiselev.ru/index.php?route=extension/tgshop/handle&api_action=products');
|
const {data} = await $fetch('/index.php?route=extension/tgshop/handle&api_action=products');
|
||||||
products.value = data;
|
products.value = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleHaptic() {
|
const carouselRef = ref();
|
||||||
window.Telegram?.WebApp?.HapticFeedback?.impactOccurred?.('heavy');
|
let lastScrollLeft = 0;
|
||||||
|
function onScroll(e) {
|
||||||
|
const scrollLeft = e.target.scrollLeft;
|
||||||
|
const delta = Math.abs(scrollLeft - lastScrollLeft);
|
||||||
|
|
||||||
|
if (delta > 30) {
|
||||||
|
hapticFeedback();
|
||||||
|
lastScrollLeft = scrollLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hapticFeedback(strength = 'light') {
|
||||||
|
if (window.Telegram.WebApp.version >= '6.1') {
|
||||||
|
window.Telegram.WebApp.HapticFeedback.impactOccurred(strength);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -12,4 +12,14 @@ export default defineConfig({
|
|||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
manifest: true,
|
manifest: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/index.php': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: path => path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user