docs: update docs
This commit is contained in:
819
docs/BANNERS_WITH_FILTERS_SPECIFICATION.md
Normal file
819
docs/BANNERS_WITH_FILTERS_SPECIFICATION.md
Normal file
@@ -0,0 +1,819 @@
|
||||
# Техническое задание: Система баннеров с динамическими фильтрами товаров
|
||||
|
||||
## 1. Описание задачи
|
||||
|
||||
### 1.1. Проблема
|
||||
|
||||
Текущий слайдер на главной странице отображает только изображения, которые не являются кликабельными. Это делает его
|
||||
бесполезным с точки зрения конверсии.
|
||||
|
||||
### 1.2. Бизнес-цель
|
||||
|
||||
Реализовать интерактивные баннеры, которые при клике будут:
|
||||
|
||||
- Открывать товары из определенной категории
|
||||
- Показывать конкретный товар
|
||||
- Отображать товары по заданному фильтру
|
||||
- Позволять администраторам создавать и сохранять фильтры для повторного использования
|
||||
|
||||
### 1.3. Техническое решение
|
||||
|
||||
- Использовать существующую систему фильтрации на основе JSON-объектов
|
||||
- Использовать стандартный функционал баннеров OpenCart
|
||||
- Создать административный интерфейс для управления фильтрами
|
||||
- Связать фильтры с баннерами через специальный формат URL в поле "ссылка" баннера
|
||||
- Добавить отслеживание целей Яндекс.Метрики
|
||||
|
||||
**Важно:** Формат ссылки `#filter:{filter_name}` с префиксом якоря `#` обеспечивает обратную совместимость:
|
||||
- На обычном сайте OpenCart ссылка не будет ломать навигацию (просто не ведет никуда)
|
||||
- В SPA JavaScript перехватывает и обрабатывает такие ссылки специальным образом
|
||||
|
||||
## 2. Архитектура решения
|
||||
|
||||
### 2.1. Компоненты системы
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ADMIN PANEL │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 1. Конструктор фильтров (Filter Builder) │
|
||||
│ - Создание JSON фильтров │
|
||||
│ - Сохранение фильтров с именами │
|
||||
│ - Редактирование и удаление фильтров │
|
||||
│ │
|
||||
│ 2. Управление баннерами (расширение OpenCart) │
|
||||
│ - Привязка фильтра к слайду через ссылку │
|
||||
│ - Формат ссылки: #filter:{filter_name} │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ BACKEND API │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 1. FilterService │
|
||||
│ - Сохранение/редактирование фильтров │
|
||||
│ - Список сохраненных фильтров │
|
||||
│ - Получение фильтра по имени │
|
||||
│ │
|
||||
│ 2. BannerHandler (расширение) │
|
||||
│ - Добавление поля filter_name в ответ │
|
||||
│ - Определение типа ссылки │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ FRONTEND SPA │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 1. Banner.vue (обновление) │
|
||||
│ - Обработка клика по слайду │
|
||||
│ - Определение типа ссылки │
|
||||
│ - Переход на соответствующую страницу │
|
||||
│ │
|
||||
│ 2. Router (обновление) │
|
||||
│ - Новый роут: /products-filter │
|
||||
│ - Параметры: filter_name или filter JSON │
|
||||
│ │
|
||||
│ 3. ProductsFilter.vue (новый компонент) │
|
||||
│ - Загрузка товаров по фильтру │
|
||||
│ - Интеграция с YaMetrika │
|
||||
│ │
|
||||
│ 4. YaMetrika Integration │
|
||||
│ - Новая цель: BANNER_CLICK │
|
||||
│ - Трекинг кликов по баннерам │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 3. Детальная спецификация
|
||||
|
||||
### 3.1. Backend: Система управления фильтрами
|
||||
|
||||
#### 3.1.1. Таблица базы данных
|
||||
|
||||
**Создать таблицу:** `oc_telecart_filter`
|
||||
|
||||
```sql
|
||||
CREATE TABLE oc_telecart_filter
|
||||
(
|
||||
filter_id INT(11) NOT NULL AUTO_INCREMENT,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
filter_json TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (filter_id),
|
||||
UNIQUE KEY unique_name (name)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8mb4;
|
||||
```
|
||||
|
||||
#### 3.1.2. Model: FilterModel
|
||||
|
||||
**Путь:** `module/oc_telegram_shop/upload/oc_telegram_shop/src/Models/FilterModel.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class FilterModel
|
||||
{
|
||||
public function create(array $data): int
|
||||
{
|
||||
// Создание нового фильтра
|
||||
}
|
||||
|
||||
public function update(int $filterId, array $data): bool
|
||||
{
|
||||
// Обновление фильтра
|
||||
}
|
||||
|
||||
public function delete(int $filterId): bool
|
||||
{
|
||||
// Удаление фильтра
|
||||
}
|
||||
|
||||
public function getById(int $filterId): ?array
|
||||
{
|
||||
// Получение фильтра по ID
|
||||
}
|
||||
|
||||
public function getByName(string $name): ?array
|
||||
{
|
||||
// Получение фильтра по имени
|
||||
}
|
||||
|
||||
public function getAll(): array
|
||||
{
|
||||
// Получение списка всех фильтров
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1.3. Handler: FilterHandler
|
||||
|
||||
**Путь:** `module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/FilterHandler.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Handlers;
|
||||
|
||||
use App\Models\FilterModel;
|
||||
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||
use Openguru\OpenCartFramework\Http\Request;
|
||||
|
||||
class FilterHandler
|
||||
{
|
||||
public function list(): JsonResponse
|
||||
{
|
||||
// GET /api/filter/list
|
||||
// Возвращает список всех сохраненных фильтров
|
||||
}
|
||||
|
||||
public function show(Request $request): JsonResponse
|
||||
{
|
||||
// GET /api/filter/show?name={filter_name}
|
||||
// Возвращает фильтр по имени
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
// POST /api/filter/store
|
||||
// Создание нового фильтра
|
||||
// Body: { name: string, filter_json: object }
|
||||
}
|
||||
|
||||
public function update(Request $request): JsonResponse
|
||||
{
|
||||
// POST /api/filter/update
|
||||
// Обновление существующего фильтра
|
||||
// Body: { id: int, name: string, filter_json: object }
|
||||
}
|
||||
|
||||
public function destroy(Request $request): JsonResponse
|
||||
{
|
||||
// POST /api/filter/delete
|
||||
// Удаление фильтра
|
||||
// Body: { id: int }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1.4. Обновление: BannerHandler
|
||||
|
||||
**Путь:** `module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/BannerHandler.php`
|
||||
|
||||
Обновить метод `show()` для поддержки фильтров:
|
||||
|
||||
```php
|
||||
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'])) {
|
||||
$link = $result['link'];
|
||||
|
||||
// Определяем тип ссылки
|
||||
$linkType = 'external';
|
||||
$filterName = null;
|
||||
|
||||
if (preg_match('/^#filter:(.+)$/', $link, $matches)) {
|
||||
$linkType = 'filter';
|
||||
$filterName = $matches[1];
|
||||
}
|
||||
|
||||
$data[] = [
|
||||
'id' => $index,
|
||||
'title' => $result['title'],
|
||||
'link' => $link,
|
||||
'link_type' => $linkType,
|
||||
'filter_name' => $filterName,
|
||||
'image' => $this->imageTool->resize($result['image'], null, 200),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2. Frontend: Компоненты SPA
|
||||
|
||||
#### 3.2.1. Обновление: Banner.vue
|
||||
|
||||
**Путь:** `spa/src/components/Banner.vue`
|
||||
|
||||
Добавить обработку клика и определение типа ссылки:
|
||||
|
||||
```vue
|
||||
|
||||
<template>
|
||||
<div v-if="slides.length > 0" class="app-banner px-4">
|
||||
<Swiper ...>
|
||||
<SwiperSlide
|
||||
v-for="slide in slides"
|
||||
:key="slide.id"
|
||||
@click="onSlideClick(slide)"
|
||||
>
|
||||
<img :src="slide.image" :alt="slide.title" class="cursor-pointer">
|
||||
</SwiperSlide>
|
||||
</Swiper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {useRouter} from 'vue-router';
|
||||
import {useYaMetrikaStore} from '@/stores/yaMetrikaStore';
|
||||
import {YA_METRIKA_GOAL} from '@/constants/yaMetrikaGoals';
|
||||
|
||||
const router = useRouter();
|
||||
const yaMetrika = useYaMetrikaStore();
|
||||
|
||||
function onSlideClick(slide) {
|
||||
yaMetrika.reachGoal(YA_METRIKA_GOAL.BANNER_CLICK, {
|
||||
banner_title: slide.title,
|
||||
link_type: slide.link_type
|
||||
});
|
||||
|
||||
if (slide.link_type === 'filter') {
|
||||
// Обработка ссылок формата #filter:{filter_name}
|
||||
// Обычные якорные ссылки не ломают навигацию в OpenCart
|
||||
router.push({
|
||||
name: 'products-filter',
|
||||
params: {filter_name: slide.filter_name}
|
||||
});
|
||||
} else if (slide.link_type === 'external') {
|
||||
// Обработка внешних ссылок (если нужно)
|
||||
console.log('External link:', slide.link);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 3.2.2. Новый компонент: ProductsFilter.vue
|
||||
|
||||
**Путь:** `spa/src/views/ProductsFilter.vue`
|
||||
|
||||
```vue
|
||||
|
||||
<template>
|
||||
<div class="pb-20">
|
||||
<ProductsList
|
||||
:products="products"
|
||||
:hasMore="hasMore"
|
||||
:isLoading="isLoading"
|
||||
:isLoadingMore="isLoadingMore"
|
||||
@loadMore="onLoadMore"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onMounted} from 'vue';
|
||||
import {useRoute} from 'vue-router';
|
||||
import {useYaMetrikaStore} from '@/stores/yaMetrikaStore';
|
||||
import ftch from '@/utils/ftch';
|
||||
import ProductsList from '@/components/ProductsList.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const yaMetrika = useYaMetrikaStore();
|
||||
const products = ref([]);
|
||||
const hasMore = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const isLoadingMore = ref(false);
|
||||
const page = ref(1);
|
||||
const perPage = 20;
|
||||
|
||||
onMounted(async () => {
|
||||
yaMetrika.pushHit(route.path, {title: 'Отфильтрованные товары'});
|
||||
|
||||
await fetchProducts();
|
||||
});
|
||||
|
||||
async function fetchProducts() {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
const filterName = route.params.filter_name;
|
||||
|
||||
// Получаем фильтр по имени
|
||||
const filterResponse = await ftch('filterShow', {name: filterName});
|
||||
const filters = filterResponse.data.filter_json;
|
||||
|
||||
// Загружаем товары с фильтром
|
||||
const response = await ftch('products', null, {
|
||||
page: page.value,
|
||||
perPage,
|
||||
filters: filters,
|
||||
});
|
||||
|
||||
products.value = response.data;
|
||||
hasMore.value = response.meta.hasMore;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function onLoadMore() {
|
||||
// Реализация загрузки следующих страниц
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 3.2.3. Обновление: Router
|
||||
|
||||
**Путь:** `spa/src/router.js`
|
||||
|
||||
Добавить новый роут:
|
||||
|
||||
```javascript
|
||||
{
|
||||
path: '/products-filter/:filter_name',
|
||||
name
|
||||
:
|
||||
'products-filter',
|
||||
component
|
||||
:
|
||||
() => import('@/views/ProductsFilter.vue')
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.4. Обновление: ftch.js
|
||||
|
||||
**Путь:** `spa/src/utils/ftch.js`
|
||||
|
||||
Добавить новые методы:
|
||||
|
||||
```javascript
|
||||
export async function fetchFilterByName(name) {
|
||||
return await ftch('filterShow', {name});
|
||||
}
|
||||
|
||||
export async function fetchFilterList() {
|
||||
return await ftch('filterList');
|
||||
}
|
||||
|
||||
export async function saveFilter(data) {
|
||||
return await apiFetch(`${BASE_URL}index.php?route=extension/tgshop/handle&api_action=filterStore`, {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.5. Обновление: yaMetrikaGoals.js
|
||||
|
||||
**Путь:** `spa/src/constants/yaMetrikaGoals.js`
|
||||
|
||||
Добавить новую цель:
|
||||
|
||||
```javascript
|
||||
export const YA_METRIKA_GOAL = {
|
||||
// ... существующие цели
|
||||
BANNER_CLICK: 'banner_click',
|
||||
};
|
||||
```
|
||||
|
||||
### 3.3. Admin Panel: Управление фильтрами
|
||||
|
||||
#### 3.3.1. Новый контроллер: FilterController
|
||||
|
||||
**Путь:** `module/oc_telegram_shop/upload/admin/controller/extension/tgshop/filter.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
class FilterController extends Controller
|
||||
{
|
||||
public function index(): void
|
||||
{
|
||||
// Список фильтров
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
{
|
||||
// Форма создания фильтра
|
||||
}
|
||||
|
||||
public function edit(): void
|
||||
{
|
||||
// Форма редактирования фильтра
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
// Сохранение фильтра
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
// Удаление фильтра
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3.2. Новая модель: FilterModel (Admin)
|
||||
|
||||
**Путь:** `module/oc_telegram_shop/upload/admin/model/extension/tgshop/filter.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
class ModelExtensionTgshopFilter extends Model
|
||||
{
|
||||
public function addFilter(array $data): int
|
||||
{
|
||||
$this->db->query("INSERT INTO " . DB_PREFIX . "telecart_filter
|
||||
SET name = '" . $this->db->escape($data['name']) . "',
|
||||
filter_json = '" . $this->db->escape(json_encode($data['filter_json'])) . "'");
|
||||
|
||||
return $this->db->getLastId();
|
||||
}
|
||||
|
||||
public function getFilter(int $filterId): ?array
|
||||
{
|
||||
$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "telecart_filter
|
||||
WHERE filter_id = '" . (int)$filterId . "'");
|
||||
|
||||
return $query->row;
|
||||
}
|
||||
|
||||
public function getFilters(): array
|
||||
{
|
||||
$query = $this->db->query("SELECT * FROM " . DB_PREFIX . "telecart_filter
|
||||
ORDER BY name ASC");
|
||||
|
||||
return $query->rows;
|
||||
}
|
||||
|
||||
public function editFilter(int $filterId, array $data): void
|
||||
{
|
||||
$this->db->query("UPDATE " . DB_PREFIX . "telecart_filter
|
||||
SET name = '" . $this->db->escape($data['name']) . "',
|
||||
filter_json = '" . $this->db->escape(json_encode($data['filter_json'])) . "'
|
||||
WHERE filter_id = '" . (int)$filterId . "'");
|
||||
}
|
||||
|
||||
public function deleteFilter(int $filterId): void
|
||||
{
|
||||
$this->db->query("DELETE FROM " . DB_PREFIX . "telecart_filter
|
||||
WHERE filter_id = '" . (int)$filterId . "'");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3.3. Новый view: Filter Builder
|
||||
|
||||
**Путь:** `module/oc_telegram_shop/upload/admin/view/template/extension/tgshop/filter.twig`
|
||||
|
||||
Создать интерфейс конструктора фильтров на основе существующего функционала из `Filters.vue`:
|
||||
|
||||
- Визуальный редактор для создания JSON фильтров
|
||||
- Поддержка всех существующих типов фильтров:
|
||||
- ProductPrice
|
||||
- ProductCategory
|
||||
- ProductManufacturer
|
||||
- ProductModel
|
||||
- ProductStatus
|
||||
- ProductQuantity
|
||||
- И другие
|
||||
- Поле для ввода имени фильтра
|
||||
- Кнопки: Сохранить, Удалить, Отменить
|
||||
|
||||
## 4. Схема работы
|
||||
|
||||
### 4.1. Создание баннера с фильтром (Администратор)
|
||||
|
||||
1. Администратор открывает конструктор фильтров в админ-панели
|
||||
2. Создает фильтр (например: "Товары со скидкой в категории Электроника")
|
||||
3. Сохраняет фильтр с именем `discount_electronics`
|
||||
4. Переходит в настройки баннеров OpenCart
|
||||
5. Создает новый слайд
|
||||
6. Загружает изображение баннера
|
||||
7. В поле "Ссылка" вводит: `#filter:discount_electronics`
|
||||
8. Сохраняет баннер
|
||||
|
||||
### 4.2. Пользователь кликает на баннер (Frontend)
|
||||
|
||||
1. Пользователь видит баннер на главной странице
|
||||
2. Кликает на баннер
|
||||
3. YaMetrika фиксирует цель `BANNER_CLICK`
|
||||
4. Frontend определяет тип ссылки: `filter`
|
||||
5. Переход на страницу `/products-filter/discount_electronics`
|
||||
6. Загрузка фильтра по имени `discount_electronics`
|
||||
7. Применение фильтра к списку товаров
|
||||
8. Отображение отфильтрованных товаров
|
||||
|
||||
### 4.3. Пример JSON фильтра
|
||||
|
||||
```json
|
||||
{
|
||||
"operand": "AND",
|
||||
"rules": {
|
||||
"RULE_PRODUCT_PRICE": {
|
||||
"criteria": {
|
||||
"product_price": {
|
||||
"type": "number",
|
||||
"params": {
|
||||
"operator": "between",
|
||||
"value": {
|
||||
"from": 1000,
|
||||
"to": 5000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"RULE_PRODUCT_CATEGORY": {
|
||||
"criteria": {
|
||||
"product_category_id": {
|
||||
"type": "product_category",
|
||||
"params": {
|
||||
"operator": "contains",
|
||||
"value": 15
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Технические детали
|
||||
|
||||
### 5.1. Маршруты API
|
||||
|
||||
**Backend (PHP):**
|
||||
|
||||
- `GET /index.php?route=extension/tgshop/handle&api_action=filterList` - список фильтров
|
||||
- `GET /index.php?route=extension/tgshop/handle&api_action=filterShow&name={name}` - получить фильтр
|
||||
- `POST /index.php?route=extension/tgshop/handle&api_action=filterStore` - создать фильтр
|
||||
- `POST /index.php?route=extension/tgshop/handle&api_action=filterUpdate` - обновить фильтр
|
||||
- `POST /index.php?route=extension/tgshop/handle&api_action=filterDelete&id={id}` - удалить фильтр
|
||||
|
||||
**Admin Panel:**
|
||||
|
||||
- `GET /admin/index.php?route=extension/tgshop/filter` - список фильтров (страница)
|
||||
- `GET /admin/index.php?route=extension/tgshop/filter/create` - форма создания
|
||||
- `GET /admin/index.php?route=extension/tgshop/filter/edit&id={id}` - форма редактирования
|
||||
- `POST /admin/index.php?route=extension/tgshop/filter/save` - сохранить фильтр
|
||||
- `POST /admin/index.php?route=extension/tgshop/filter/delete&id={id}` - удалить фильтр
|
||||
|
||||
### 5.2. Валидация
|
||||
|
||||
- Имя фильтра: обязательно, уникальное, максимально 255 символов
|
||||
- JSON фильтра: обязательно, валидный JSON, соответствует существующей схеме фильтров
|
||||
- Проверка существования всех критериев в системе
|
||||
|
||||
### 5.3. Безопасность
|
||||
|
||||
- Проверка прав доступа для админ-панели
|
||||
- SQL Injection защита через prepared statements
|
||||
- XSS защита при выводе данных
|
||||
- CSRF защита для форм администратора
|
||||
|
||||
### 5.4. Обратная совместимость
|
||||
|
||||
Использование формата ссылки `#filter:{filter_name}` с префиксом `#` гарантирует:
|
||||
|
||||
- **Совместимость с OpenCart**: При установке баннеров на обычный сайт OpenCart, якорная ссылка не приведет к ошибке 404, так как браузер просто оставит пользователя на текущей странице
|
||||
- **Работа в SPA**: В Telegram веб-приложении JavaScript перехватывает клики и обрабатывает их специальным образом
|
||||
- **Отказоустойчивость**: Если JavaScript не загрузился, пользователь не увидит ошибку, просто ничего не произойдет при клике
|
||||
|
||||
## 6. Интеграция с Яндекс.Метрикой
|
||||
|
||||
### 6.1. Цели метрики
|
||||
|
||||
Добавить в `spa/src/constants/yaMetrikaGoals.js`:
|
||||
|
||||
```javascript
|
||||
BANNER_CLICK: 'banner_click',
|
||||
```
|
||||
|
||||
### 6.2. События
|
||||
|
||||
- `banner_click` - клик по баннеру
|
||||
- Параметры: `banner_title`, `link_type`, `filter_name`
|
||||
|
||||
### 6.3. Ecommerce события
|
||||
|
||||
При загрузке отфильтрованных товаров отправлять событие `view_item_list`:
|
||||
|
||||
```javascript
|
||||
yaMetrika.dataLayerPush({
|
||||
ecommerce: {
|
||||
items: products.map((product, index) => ({
|
||||
item_id: product.id,
|
||||
item_name: product.name,
|
||||
price: product.final_price_numeric,
|
||||
item_category: product.category_name,
|
||||
quantity: 1,
|
||||
discount: product.price_numeric - product.final_price_numeric,
|
||||
})),
|
||||
item_list_id: 'banner_filter',
|
||||
item_list_name: 'Отфильтрованные товары',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 7. План реализации
|
||||
|
||||
### Этап 1: Backend (API для управления фильтрами)
|
||||
|
||||
- [ ] Создать таблицу `oc_telecart_filter`
|
||||
- [ ] Создать модель `FilterModel`
|
||||
- [ ] Создать хэндлер `FilterHandler` с методами CRUD
|
||||
- [ ] Добавить маршруты в `routes.php`
|
||||
- [ ] Обновить `BannerHandler` для поддержки фильтров
|
||||
|
||||
### Этап 2: Frontend (SPA компоненты)
|
||||
|
||||
- [ ] Создать компонент `ProductsFilter.vue`
|
||||
- [ ] Обновить `Banner.vue` для обработки клика
|
||||
- [ ] Добавить маршрут в `router.js`
|
||||
- [ ] Добавить методы в `ftch.js`
|
||||
- [ ] Добавить цель `BANNER_CLICK` в YaMetrika
|
||||
|
||||
### Этап 3: Admin Panel (Конструктор фильтров)
|
||||
|
||||
- [ ] Создать контроллер `FilterController` в админке
|
||||
- [ ] Создать модель `ModelExtensionTgshopFilter`
|
||||
- [ ] Создать шаблоны для списка и форм
|
||||
- [ ] Реализовать визуальный конструктор фильтров
|
||||
|
||||
### Этап 4: Тестирование
|
||||
|
||||
- [ ] Unit тесты для модели фильтров
|
||||
- [ ] Интеграционные тесты для API
|
||||
- [ ] Функциональные тесты в SPA
|
||||
- [ ] Тестирование интеграции с Яндекс.Метрикой
|
||||
|
||||
### Этап 5: Документация
|
||||
|
||||
- [ ] Документация для администраторов
|
||||
- [ ] API документация
|
||||
- [ ] Описание схемы JSON фильтров
|
||||
|
||||
## 8. Возможные расширения (будущее)
|
||||
|
||||
1. **Предпросмотр фильтров** - возможность увидеть количество товаров, соответствующих фильтру
|
||||
2. **Предустановленные фильтры** - шаблоны популярных фильтров
|
||||
3. **A/B тестирование баннеров** - ротация баннеров с отслеживанием конверсии
|
||||
4. **Аналитика кликов** - статистика по каждому баннеру
|
||||
5. **Многовариантные фильтры** - баннер может открывать несколько результатов
|
||||
|
||||
## 9. Примеры использования
|
||||
|
||||
### Пример 1: Баннер "Скидки до 50%"
|
||||
|
||||
**Фильтр:**
|
||||
|
||||
```json
|
||||
{
|
||||
"operand": "AND",
|
||||
"rules": {
|
||||
"RULE_PRODUCT_SPECIAL": {
|
||||
"criteria": {
|
||||
"has_special": {
|
||||
"type": "boolean",
|
||||
"params": {
|
||||
"operator": "equals",
|
||||
"value": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ссылка в баннере:** `#filter:specials_50_discount`
|
||||
|
||||
### Пример 2: Баннер "Новинки в категории"
|
||||
|
||||
**Фильтр:**
|
||||
|
||||
```json
|
||||
{
|
||||
"operand": "AND",
|
||||
"rules": {
|
||||
"RULE_PRODUCT_CATEGORY": {
|
||||
"criteria": {
|
||||
"product_category_id": {
|
||||
"type": "product_category",
|
||||
"params": {
|
||||
"operator": "contains",
|
||||
"value": 42
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"RULE_PRODUCT_STATUS": {
|
||||
"criteria": {
|
||||
"product_status": {
|
||||
"type": "boolean",
|
||||
"params": {
|
||||
"operator": "equals",
|
||||
"value": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ссылка в баннере:** `#filter:new_in_electronics`
|
||||
|
||||
### Пример 3: Баннер "Товары до 1000₽"
|
||||
|
||||
**Фильтр:**
|
||||
|
||||
```json
|
||||
{
|
||||
"operand": "AND",
|
||||
"rules": {
|
||||
"RULE_PRODUCT_PRICE": {
|
||||
"criteria": {
|
||||
"product_price": {
|
||||
"type": "number",
|
||||
"params": {
|
||||
"operator": "between",
|
||||
"value": {
|
||||
"from": 0,
|
||||
"to": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ссылка в баннере:** `#filter:budget_under_1000`
|
||||
|
||||
## 10. Выводы
|
||||
|
||||
Данное техническое задание описывает полную систему для реализации интерактивных баннеров с динамической фильтрацией
|
||||
товаров. Реализация не нарушает существующую архитектуру проекта и полностью использует уже имеющийся функционал
|
||||
фильтрации.
|
||||
|
||||
Основные преимущества:
|
||||
|
||||
- Гибкость настроек для администраторов
|
||||
- Масштабируемость (можно добавить новые типы фильтров)
|
||||
- Интеграция с Яндекс.Метрикой для аналитики
|
||||
- Простота использования для администраторов магазина
|
||||
- Обратная совместимость с существующими баннерами
|
||||
- **Гарантированная совместимость с OpenCart**: использование якорных ссылок `#filter:{filter_name}` позволяет устанавливать баннеры как на SPA, так и на обычный сайт без ошибок
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
🎁 **ГЛАВНЫЕ НОВОСТИ:**
|
||||
|
||||
💰 **Поддержка купонов и ваучеров**
|
||||
Telecart теперь поддерживает встроенную систему купонов OpenCart! Используйте нативные купоны и ваучеры для скидок и акций.
|
||||
💰 **Поддержка купонов и подарочных сертификатов**
|
||||
Telecart теперь поддерживает встроенную систему купонов OpenCart! Используйте нативные купоны и подарочных сертификаты для скидок и акций.
|
||||
|
||||
📊 **Улучшенная Яндекс.Метрика**
|
||||
Теперь с поддержкой ecommerce, хитов и целей! Полная воронка продаж:
|
||||
@@ -14,8 +14,8 @@ Telecart теперь поддерживает встроенную систем
|
||||
Уникальный ID покупателя позволяет анализировать поведение одного пользователя на всех устройствах.
|
||||
📖 Документация: https://telecart-labs.github.io/docs/analitycs/start/
|
||||
|
||||
🖼️ **Баннеры на главной странице**
|
||||
Используйте встроенные баннеры OpenCart для акций и промо. Привлекайте внимание покупателей к важным предложениям!
|
||||
🖼️ **Баннеры с умной фильтрацией — продавайте то, что нужно именно сейчас**
|
||||
Новинка для вашего Telegram-магазина! Теперь каждый баннер ведет клиента именно на те товары, которые вы хотите продвигать. Запустили акцию на скидки? Создайте баннер "Скидки до 50%" — и он автоматически покажет только товары со скидками. Нужно продать премиум-сегмент? Баннер "Товары от 1000₽" направит клиентов на нужный диапазон. Увеличивайте конверсию и контролируйте, какие товары видит покупатель после клика по баннеру. Встроенная фильтрация по цене, категориям, производителям делает ваши маркетинговые кампании максимально эффективными и результативными!
|
||||
|
||||
✏️ **Кастомизация текстов приложения**
|
||||
Теперь можно настраивать ключевые тексты:
|
||||
Reference in New Issue
Block a user