Готовые сценарии — первый шаг
Плагины работают на площадках GGsel, FunPay и Playerok. Плагин описывает данные, действия и независимые блоки. Lovsel формирует навигацию, размещение, стандартные состояния, главное действие и настройку. GGsel, FunPay и Playerok используют один контракт. Плагин по-прежнему объявляет одну площадку в marketplace; это область его данных, а не отдельный UI SDK.
| Сценарий | Шаблон | Готовые блоки |
|---|---|---|
| Автоматизация | automation | Обзор, автоматизация, журнал, инструкция |
| Поставщик и закупка | supplier | Обзор, товары, заказы, связки, автоматизация, журнал, инструкция |
| Автовыдача | delivery | Обзор, заказы, связки, автоматизация, журнал, инструкция |
| Цены и остатки | pricing | Обзор, товары, автоматизация, журнал, инструкция |
| Работа с заказами | orders | Обзор, заказы, журнал, инструкция |
| Сообщения и отзывы | messages | Обзор, заказы, настройки, журнал, инструкция |
| Аналитика и отчёты | analytics | Обзор, статистика, журнал, инструкция |
| Синхронизация сервисов | sync | Обзор, товары, связки, автоматизация, журнал, инструкция |
app = WebPlugin(MANIFEST, ui_version=3, layout="hybrid", settings_panel=True)
app.settings([ui.field.switch("enabled", "Включить обработку", default=False)])
app.use_template("supplier", handlers={"bindings": bindings, "automation": automation})
app.setup_marketplace()bindings(ctx, params) и automation(ctx, params) — ваши функции, возвращающие ui.* блоки. Шаблон даёт работающий каркас, читает товары/заказы через SDK и показывает сохранённые данные. Неуказанные бизнес-обработчики получают понятное пустое состояние. Шаблон не выполняет закупку, выдачу, изменение цен или отправку сообщений сам: эти операции явно реализуются в действиях и событиях. Для новых шаблонов автоматически появляется главное действие «Обновить». Его можно заменить через primary_action.
Полные примеры: GGsel, FunPay, Playerok. У всех трёх одинаковая структура и четыре варианта компоновки.
Выбор компоновки
| Режим | Как выглядит | Когда выбирать |
|---|---|---|
single | Все нескрытые блоки и обычные страницы в одном рабочем экране | Один сценарий, мало настроек, быстрый обзор |
tabs | Страницы и разделы во вкладках сверху | Несколько равноправных разделов без глубокой вложенности |
sidebar | Отдельное внутреннее меню внутри страницы плагина | Много разделов: товары, заказы, связки, журнал, инструкция |
hybrid | Крупные разделы сверху; внутри выбранного раздела — собственное боковое меню | Сложный плагин с несколькими рабочими областями |
Левое меню Lovsel не заполняется десятками страниц плагина. В нём остаётся сам плагин. Внутренняя панель находится внутри его экрана. На телефоне она становится горизонтальным списком. Цвет берётся из площадки: GGsel — зелёный, FunPay — голубой, Playerok — синий. Не задавайте свой CSS.
app.section("work", "Рабочее пространство", icon="package")
@app.block("orders", kind="orders", section="work", width=8, order=1,
requires_connection=True)
def orders(ctx, params):
return [ui.market_table("orders", ctx.market.orders(), marketplace=ctx.marketplace)]
@app.block("stats", kind="statistics", section="work", width=4, order=2)
def statistics(ctx, params):
return [ui.stats([ui.stat("Связок", len(ctx.storage.get("bindings", [])))])]Теперь поменяем представление без изменения этих функций:
app.compose("single")
app.compose("tabs", {"orders": {"page": "orders"}, "stats": {"page": "statistics"}})
app.compose("sidebar")
app.compose("hybrid", {"orders": {"section": "work"}, "stats": {"section": "work"}})compose(layout, placements) изменяет только page, section, width и order существующих блоков. Функции, настройки, события, данные и действия не перерегистрируются. Неверная ширина и неизвестный блок вызывают ошибку до применения размещения. Скрытые страницы с app.page(hidden=True) остаются отдельными карточками/редакторами, а не добавляются в общий экран single.
Независимые блоки и конструктор страницы
app.block(id, title=None, kind="overview", page=None, section=None, width=12, order=0, requires_connection=False) регистрирует функцию (ctx, params) → список блоков.
Стандартные виды: overview, statistics, products, orders, bindings, automation, settings, logs, help. У products/orders подписи автоматически меняются на лоты/продажи/сделки. Свой title переопределяет подпись, но не меняет поведение данных.
- Сетка — 12 колонок.
widthот 1 до 12;order— порядок чтения и размещения. - По умолчанию каждый блок получает страницу со своим ID. Одинаковый
pageобъединяет блоки на одной странице. sectionссылается наapp.section; разделы могут вкладываться по прежнему контрактуparent.- Мобильная версия автоматически размещает панели в одну колонку с тем же порядком.
- Ошибка отдельного блока показывает стандартное состояние и ссылку на журнал; другие блоки продолжают строиться.
requires_connection=Trueпоказывает подключение правильной площадки вместо вызова обработчика без аккаунта.
Для динамических страниц можно вернуть сетку напрямую:
return [ui.canvas([
ui.panel("overview", overview_blocks, title="Обзор", width=8, order=0),
ui.panel("statistics", statistics_blocks, title="Статистика", width=4, order=1),
])]ui.grid() сохраняет старый контракт 1–4 колонок. Его не нужно переписывать.
Состояния, кнопки и настройки
ui.state("loading", title="Обновляем данные")
ui.state("empty", description="Добавьте первую связку", action=ui.button("Добавить", page="bindings"))
ui.state("error", description="Повторите проверку", action=ui.button("Проверить", "check"))
ui.state("disconnected", marketplace=ctx.marketplace)Все состояния отображаются одинаковыми компонентами Lovsel. Страница также автоматически имеет загрузку, повтор после ошибки и пустое состояние. Не заменяйте их собственными HTML-экранами.
app = WebPlugin(MANIFEST, ui_version=3, layout="sidebar", settings_panel=True,
primary_action=ui.button("Проверить данные", "check", variant="primary"))Главное действие закрепляется в шапке. Это обычное действие SDK: сохраняются confirm, индикация ожидания, ошибки, обновление данных и права доступа. У выключенного или ошибочного плагина оно не предлагается; у плагина, ожидающего подключения, главное действие ведёт в доступный мастер настройки. primary_action также может быть функцией (ctx) → ui.button(...), например «Исправить подключение» или «Включить автоматизацию» по текущему состоянию.
settings_panel=True открывает встроенные настройки справа, включая переход из события к __settings. Формат app.settings и серверная проверка значений не меняются. Секретные поля не возвращаются в браузер. Для отдельных групп используйте ui.settings_panel(label, groups, action=..., values=...) или ui.button(dialog=ui.dialog(..., side=True)).
Готовые компоненты и совместимость
| Компонент | GGsel | FunPay | Playerok |
|---|---|---|---|
ui.product_picker | Товары | Лоты | Товары |
ui.market_table("products", ...) | Товары | Лоты | Товары |
ui.market_table("orders", ...) | Заказы | Продажи | Сделки |
ui.bindings_editor | Да | Да | Да |
ui.automation_status | Да | Да | Да |
app.setup_marketplace / app.onboarding_step | Да | Да | Да |
ui.logs / ui.operations / ui.progress | Да | Да | Да |
ui.chart / ui.stats | Да | Да | Да |
ui.message_editor / ui.message_preview | Да | Да | Да |
ui.settings_panel / ui.settings | Да | Да | Да |
ui.canvas / ui.panel / ui.state | Да | Да | Да |
single / tabs / sidebar / hybrid | Да | Да | Да |
ui.product_picker("product_id", marketplace=ctx.marketplace, required=True)
ui.bindings_editor("binding", rows, marketplace=ctx.marketplace, action="save_binding")
ui.message_editor("message", action="save_message", value=current_message,
variables=[ui.var("order_id", "Номер заказа", "101")])
ui.automation_status(enabled, pending=waiting_count, error=last_error)
ui.operations() # операции, созданные через ctx.operationsВыбор товара использует встроенный серверный поиск __market_products и права products.read. ID из формы всегда перепроверяется в бизнес-действии: сам список выбора не заменяет проверку принадлежности. Редактор связок передаёт product_id и external_id в data (поля data["product_id"] и data["external_id"]); сохранение делает ваше действие. ui.market_table принимает действия строк. У действия можно указать requires="raise_lots" или другую возможность из ctx.market.info["actions"]: неподходящие площадке кнопки не показываются. Серверные разрешения всё равно проверяются адаптером при выполнении действия.
Данные и особенности площадок
ctx.market.id, ctx.market.info, ctx.market.connected, ctx.market.products(), ctx.market.orders(), ctx.market.verify() дают единый read-only контракт. Нормализованные объекты имеют id, title, status и raw с исходными данными. Остальные поля сохраняются; единицы цены не преобразуются автоматически.
| Метод / действие | GGsel | FunPay | Playerok |
|---|---|---|---|
ctx.market.products() | ctx.ggsel.products() | ctx.funpay.lots() | ctx.playerok.items() |
ctx.market.orders() | ctx.ggsel.orders() | ctx.funpay.orders() | ctx.playerok.orders() |
| Проверить подключение чтением | ctx.market.verify() | ctx.market.verify() | ctx.market.verify() |
| Цена | ctx.ggsel.set_price | ctx.funpay.set_lot_price | ctx.playerok.set_item_price |
| Изменить товар/лот | ctx.ggsel.update_product | ctx.funpay.save_lot | ctx.playerok.update_item |
| Остатки | Поля товара / Seller API | Поля лота по возможностям категории | Данные товара по возможностям площадки |
| Переписка | ctx.ggsel.messages/send_message | ctx.funpay.messages/send_message | ctx.playerok.messages/send_message |
| Отзывы | ctx.ggsel.reviews | ctx.funpay.reply_review (ответ на отзыв продажи) | ctx.playerok.reviews |
| Поднятие лотов | — | ctx.funpay.raise_lots | — |
| Публикация | Возможности Seller API | Сохранение/активность лота | publish_options/publish_item |
| Выдача / проблема сделки | События заказов и бизнес-обработчик | Продажа и бизнес-обработчик | deliver/delivery, событие playerok.deal_problem |
| Низкоуровневый API | ctx.ggsel.request | ctx.funpay.request | ctx.playerok.graphql |
Не имитируйте отсутствующее действие универсальной кнопкой. Перед специфичной кнопкой проверьте ctx.market.supports("raise_lots"), "publish", "delivery" и т. п. Для записи используйте реальный адаптер площадки и его контракт. Права (products.write, orders.read, chats.write, marketplace.api и др.) по-прежнему обязательны. API поставщика подключается отдельно через ctx.http; никакой конкретный поставщик не встроен в архитектуру интерфейса.
Первоначальная настройка
app.setup_marketplace(version="1")
@app.onboarding_step("binding", "Создайте связку",
complete=lambda ctx: bool(ctx.storage.get("bindings", [])))
def first_binding(ctx, params):
return bindings(ctx, params)Готовые шаги одинаковы для трёх площадок: подключить аккаунт, затем проверить чтение объявленных данных. Мастер SDK 3 доступен ещё до подключения аккаунта. В этом режиме Lovsel создаёт только контекст настройки: on_start, старый register и обработчики событий не запускаются. Бизнес-страницы и действия закрыты до подключения; после подключения плагин загружается обычным способом. Это не меняет запуск старых плагинов. Для этого объявите storage; результат проверки хранится в пространстве данного продавца и плагина. После смены аккаунта нужна новая проверка. version позволяет повторить проверку после изменения требований. Проверка не выполняет тестовых закупок, не отправляет сообщения и не публикует товары.
Дополнительный шаг состоит из id, title, серверной функции complete(ctx) и функции интерфейса. required=False делает шаг необязательным. Lovsel показывает число выполненных шагов и следующее обязательное действие. Завершение определяется данными на сервере, а не параметром запроса из браузера. После завершения обязательных шагов мастер автоматически заменяется обычным интерфейсом. Ссылка на отдельную страницу остаётся доступной: с неё можно продолжить настройку.
Центр событий: причина, последствия, решение
ctx.notify(
"Не удалось обновить остатки", level="error", needs_action=True,
marketplace=ctx.marketplace, plugin_id=ctx.plugin_key,
cause="Сервис поставщика временно не ответил.",
consequence="В плагине остаются ранее сохранённые остатки.",
solution="Проверьте подключение и повторите синхронизацию.",
page="bindings", section="connection", params={"id": "101"},
primary_action={"label": "Проверить настройки", "entity": "settings"},
secondary_action={"label": "Открыть связку", "entity": "binding", "id": "101"},
group_key="supplier:connection",
)Новые поля: plugin_id, page, section, params, primary_action, secondary_action, group_key, cause, consequence, solution. plugin_id может быть ID из манифеста либо текущим ctx.plugin_key; сохранённая принадлежность всегда определяется настоящим контекстом плагина.
Цели действия: page, settings, binding, order, deal, product, lot. Для объекта нужен id. Страница и параметры открываются внутри плагина; заказ/товар/лот/сделка — в правильном разделе площадки. section прокручивает страницу к соответствующему ui.panel(id=...); на странице __settings можно указать ключ поля, например section="enabled": панель откроется справа и поле получит фокус. Для произвольной связки укажите свою page; по умолчанию используется стандартная страница bindings. Действия уведомления — навигация, не скрытые запросы на покупку/выдачу/изменение данных.
Одинаковый group_key обновляет одну карточку, увеличивает счётчик и сохраняет последнее состояние. Область объединения: пользователь + установленный плагин + площадка + ключ. Другие продавцы и плагины не смешиваются. Обработанное событие может снова стать требующим действия. Для разных заказов используйте разные ключи, например order:<id>:delivery. Telegram использует тот же ключ для подавления повторов. Без group_key сохраняется прежнее поведение. Старый ctx.notify(title, body, ...) тоже работает: Lovsel добавляет стандартное объяснение и кнопку перехода, если автор не передал новые поля.
От простого плагина к сложному
- Начните с
ordersилиautomationи одногоsingle-экрана. - Замените обработчик обзора в
handlers; добавьте реальные показатели иui.stateдля отсутствующих данных. - Добавьте
app.settings,app.setup_marketplaceи один обязательный бизнес-шаг. - Подключите
ui.bindings_editor, серверную проверку ID и сохранение связок вctx.storage. - Вынесите чтение товаров и заказов в отдельные блоки; переключите
compose("tabs"). - При росте разделов используйте
sidebar, а при появлении рабочих областей —hybridсapp.section. - Вынесите длительные задачи в
ctx.operations, показывайтеui.operations/ui.progressи журнал. - Добавьте события с объяснением, конкретными переходами и
group_key. - Проверьте все четыре компоновки на трёх наборах данных через
lovsel_sdk.testing.
Не связывайте бизнес-логику с номером вкладки, цветом, шириной или порядком карточек. Функции данных и действий должны работать одинаково при переносе блока.
Обратная совместимость и переход
- SDK 3 экспортирует прежние
WebPlugin,ui,PluginContext, ошибки, формы, операции и адаптеры площадок. - Без
ui_version: 3старый плагин сохраняет прежнюю навигацию и API страниц. Он автоматически оборачивается стандартной шапкой Lovsel и общими состояниями. Старыеsidebar/autoостаются совместимыми вкладками. - Для новой компоновки достаточно
ui_version: 3в манифесте илиWebPlugin(..., ui_version=3). Новыеlayout="single"/"hybrid",app.block,app.compose,use_templateи шаги мастера включают SDK 3 явно. app.pageне удалён: в SDK 3 страницы участвуют в выбранной навигации, а вsingleих содержимое автоматически собирается в общий экран. Скрытые карточки по-прежнему открываются отдельно.- Настройки и данные продавца не переименовываются;
ctx.ggsel,ctx.funpay,ctx.playerokне заменены. - Миграция центра событий добавляет только колонки с пустыми значениями и индекс, не удаляя историю.
- Файл сложного плагина стороннего сервиса — пример требований, а не зависимость платформы.
Проверка перед публикацией
Проверьте пустой магазин, отсутствие подключения, отказ прав, ошибку блока, повтор события и несколько продавцов с одинаковыми ID товаров. Затем — светлую/тёмную темы, ширину 390 px, все четыре компоновки, доступность основного действия, сохранение настроек справа и переходы из событий. Полные примеры ниже можно проверять без аккаунтов площадок: стенд SDK принимает отдельные наборы products/orders, funpay={lots,orders} и playerok={items,orders}.
Частые вопросы
Нужен ли отдельный интерфейс для каждой площадки?
Нет. GGsel, FunPay и Playerok используют один SDK и одни компоненты. Площадка определяет данные, доступные действия, названия объектов и цвет, а не несовместимый набор элементов.
Можно ли сменить компоновку без переписывания логики?
Да. Выберите single, tabs, sidebar или hybrid через layout или app.compose. Функции блоков и действий остаются прежними; меняются только страницы, разделы, ширина и порядок размещения.
Старый плагин продолжит работать после обновления?
Да. Прежние app.page, формы, настройки и адаптеры сохранены. Новый интерфейс включается через ui_version=3 или новые методы композиции. Данные продавца не нужно переносить в новый формат.