Содержание
Плагин описывает, что показать: разделы, поля, таблицы, кнопки и действия. Как это выглядит, решает Lovsel. Каждая функция ui.* возвращает обычный словарь, а кабинет отрисовывает его своими компонентами в стиле shadcn/ui: карточки, таблицы с фильтрами, формы, вкладки, мастера и предпросмотр сообщений. Поэтому плагин выглядит частью Lovsel, работает в светлой и тёмной теме и на телефоне. HTML, CSS и JavaScript писать не нужно.
Структура файла, манифест, события и публикация описаны в руководстве «Как создать плагин». API площадок — в разделах «Плагины для GGsel» и «Плагины для FunPay».
from lovsel_sdk import WebPlugin, ui, ValidationErrorЦвет площадки
Кнопки, активные пункты меню, переключатели, графики и пузыри сообщений окрашиваются в цвет площадки автоматически. По цвету всегда понятно, с какой площадкой работает пользователь.
| Плагин | Цвет |
|---|---|
"marketplaces": ["ggsel"] | зелёный GGsel |
"marketplaces": ["funpay"] | голубой FunPay |
| Обе площадки | площадка, выбранная в переключателе кабинета; в режиме «Все площадки» — фирменный цвет Lovsel |
@app.page(..., marketplace="funpay") | цвет этой площадки только на этой странице (для плагина обеих площадок) |
Цвет в плагине не задаётся: у блоков есть только смысловые оттенки tone — success, warning, danger, info, neutral и accent (цвет площадки).
Навигация плагина
У плагина своё меню. Каждая страница — функция, которая возвращает список блоков.
app = WebPlugin(MANIFEST, layout="sidebar")
WORKSPACE = "Рабочее пространство"
@app.page("overview", "Обзор", icon="layout-dashboard", group=WORKSPACE)
def overview(ctx, params): ...
@app.page("rules", "Правила", icon="list-checks", group=WORKSPACE, badge=lambda ctx: len(rules(ctx)) or None)
def rules_page(ctx, params): ...
@app.page("rule", "Правило", hidden=True) # открывается кнопкой, в меню её нет
def rule_page(ctx, params): ...
@app.page("help", "Инструкция", icon="book", bottom=True)
def help_page(ctx, params): ...| Параметр | Что делает |
|---|---|
layout у WebPlugin | "sidebar" — меню слева внутри плагина, "tabs" — вкладки сверху, "auto" (по умолчанию) — меню слева, если страниц больше трёх или есть группы. На телефоне меню всегда превращается во вкладки. |
icon | Имя иконки (список — в конце статьи) или эмодзи. |
description | Подзаголовок страницы. |
group | Заголовок группы в меню: «Рабочее пространство», «Справка». |
badge | Функция badge(ctx): число или строка рядом с названием. {"value": 3, "tone": "danger"} — счётчик другого цвета. None или 0 — без счётчика. |
hidden=True | Страницы нет в меню: карточка записи, мастер создания. Открывается кнопкой ui.button(page=..., params=...). |
bottom=True | Пункт внизу меню («Инструкция»). |
marketplace | Цвет площадки для страницы плагина обеих площадок. |
Lovsel сам добавляет в меню группу «Сервис»: «Настройки» (если объявлены app.settings), «Журнал» и «О плагине».
Если страница начинается с ui.header, Lovsel показывает его вместо стандартного заголовка страницы.
Параметры страницы
Второй аргумент страницы — params, словарь параметров. Они хранятся в адресе страницы: обновление вкладки и кнопка «Назад» в браузере работают, ссылку на запись можно переслать.
Параметры задают:
- кнопка
ui.button("Открыть", page="rule", params={"id": rule_id}); - ответ действия
ui.result(page="rule", params={"id": rule_id}); - строка таблицы: кнопка с
page=вrow_actionsилиrow_clickпередаётparams["id"]— значение поляrow_keyстроки (по умолчаниюid); - поле поиска и фильтр в
ui.toolbar— параметр с их ключом; - вкладки
ui.tabs(..., param="tab").
@app.page("rule", "Правило", hidden=True)
def rule_page(ctx, params):
rule = rules(ctx).get(str(params.get("id") or ""))
if rule is None:
return [ui.header("Правило не найдено", back="rules"), ui.empty("Правило удалено или ссылка устарела.")]
...Всегда проверяйте, что запись из params существует: ссылка могла устареть.
Шапка страницы
ui.header("Правила", "Товар GGsel → текст благодарности.",
actions=[ui.button("Новое правило", page="rule_new", variant="primary", icon="plus")],
back=ui.button("Все правила", page="rules"),
badge=ui.badge("Работает", "success"),
eyebrow="Рабочее пространство")back — id страницы или кнопка; badge — бейдж рядом с заголовком; eyebrow — строка над заголовком. Главная кнопка страницы — одна, с variant="primary".
Раскладка
| Блок | Назначение |
|---|---|
ui.card(title, children, description=, actions=, footer=, tone=, flush=, id=, image=, image_ratio=) | Карточка — основной контейнер. actions — кнопки в шапке, footer — кнопки в подвале, tone — подсветка (warning, danger, info, success, accent), flush=True — содержимое без отступов, image — обложка над заголовком (см. «Изображения»). |
ui.grid(children, columns=2, widths=[2, 1]) | Сетка из 1–4 колонок; widths — разная ширина колонок. На телефоне — одна колонка. |
ui.section(title, children, description=, actions=) | Раздел с подзаголовком без рамки. |
ui.tabs([ui.tab(id, label, children, badge=, icon=)], value=, param=, style=) | Вкладки внутри страницы. style="underline" — вкладки с подчёркиванием. |
ui.details(title, children, description=, open=, icon=) | Сворачиваемый блок: редкие настройки, пояснения, опасные действия. |
ui.divider(label=) | Разделитель, можно с подписью. |
ui.grid([
ui.card("Требуют внимания", [ui.list_view(attention, empty_text="Всё отправлено.")]),
ui.card("Отправки за 14 дней", [ui.chart(days, kind="bar", label="Отправки по дням")]),
], columns=2, widths=[3, 2])Вкладки. Без param содержимое всех вкладок приходит сразу и переключается без запроса к плагину. С param="tab" выбранная вкладка попадает в params["tab"], страница перерисовывается, и плагин может отдать только нужную вкладку — так делают, когда вкладки тяжёлые.
Текст
| Блок | Назначение |
|---|---|
ui.text(text, tone=, size=, muted=) | Абзац. Поддерживает **жирный** и ` код . size: sm, md, lg`. |
ui.heading(text, level=2) | Подзаголовок. |
ui.alert(text, title=, tone=, action=) | Заметное сообщение: info, success, warning, danger. action — кнопка справа. |
ui.code(text, language=, copy=True) | Код или лог моноширинным шрифтом с кнопкой «Скопировать». |
ui.image(src, alt=, height=, caption=, ratio=, ...) | Картинка: загруженная в Lovsel (ui.asset("имя.png")), https:// или data:image/.... Подробно — в разделе «Изображения». |
ui.empty(title, description=, action=, icon=) | Пустое состояние: что здесь появится и что сделать. |
Пустое состояние обязательно, если данных может не быть:
ui.empty("Правил пока нет", "Создайте правило: выберите товар и напишите текст.", icon="message-square",
action=ui.button("Создать первое правило", page="rule_new", variant="primary"))Изображения
Баннеры, превью, скриншоты и картинки для инструкций загружаются в кабинете разработчика: «Разработчикам» → плагин → вкладка «Разработка» → «Изображения». В коде на картинку ссылаются по имени файла через ui.asset — Lovsel сам подставит адрес, поэтому картинка одинаково открывается у вас и у всех покупателей, на компьютере и на телефоне. Файл с тем же именем заменяет прежний: код менять не нужно.
| Блок | Назначение |
|---|---|
ui.asset("banner.png") | Ссылка на загруженную картинку. Подходит везде, где нужна картинка: ui.image, ui.banner, ui.gallery, ui.card(image=), ui.item(image=), колонка таблицы с format="image". Расширение можно не писать, если имя одно. |
ui.image(src, alt=, height=, caption=, ratio=, fit=, width=, zoom=, align=) | Картинка. caption — подпись, ratio — пропорции рамки (21:9, 16:9, 3:2, 4:3, 1:1, 3:4), height — высота на компьютере в пикселях, fit="contain" — показать целиком без обрезки, width — наибольшая ширина, align="center" — по центру. По нажатию открывается во весь экран (zoom=False — выключить). |
ui.banner(image, title=, description=, actions=, eyebrow=, ratio="21:9", align=, overlay=True) | Баннер во всю ширину: картинка, заголовок, текст и кнопки поверх неё. На телефоне баннер становится выше, чтобы текст и кнопки поместились. Без title и description — просто широкая картинка. |
ui.gallery(images, columns=3, ratio="4:3", fit=, zoom=True) | Галерея: превью, скриншоты, шаги инструкции. Элементы — ui.asset(...), ссылки или {"src": ..., "caption": ...}. На телефоне — две колонки; по нажатию картинки листаются во весь экран. |
ui.card(..., image=ui.asset("cover.png"), image_ratio="16:9") | Карточка с обложкой. |
ui.item(..., image=ui.asset("lot.png")) | Миниатюра слева в ui.list_view. |
@app.page("guide", "Инструкция", icon="book")
def guide(ctx, params):
return [
ui.banner(ui.asset("banner.png"), "Автовыдача за 3 шага", "Настройка займёт пару минут.",
actions=[ui.button("Начать", page="rules", variant="primary")]),
ui.gallery([
{"src": ui.asset("step-1.png"), "caption": "1. Выберите товар"},
{"src": ui.asset("step-2.png"), "caption": "2. Напишите текст"},
{"src": ui.asset("step-3.png"), "caption": "3. Включите правило"},
], columns=3, ratio="16:9"),
ui.image(ui.asset("result.png"), caption="Так покупатель увидит сообщение", width=520),
]Ограничения: PNG, JPEG, WebP или GIF, до 2 МБ на файл, до 40 файлов и 20 МБ на плагин. SVG не принимается — в нём может быть скрипт. Тип файла проверяется по содержимому, а не по расширению. Если картинку с указанным именем не загрузили, вместо неё будет заглушка «Изображение не загружено» — тестовый стенд покажет такие места заранее (см. «Проверка страниц»).
Значения
Значения для ячеек таблиц, ui.keyvalue и показателей:
| Функция | Результат |
|---|---|
ui.badge(label, tone, value=, dot=) | Бейдж статуса. value — код для фильтров таблицы («on», «off»). |
ui.value(v, format=, currency=, subtitle=) | Значение с форматом: ui.value(2450, format="money", currency="RUB"). |
ui.link(label, href) | Внешняя ссылка https://. |
ui.money(value, currency) | Готовая строка суммы: ui.money(1234.5, "USD") → «1 234,50 $». |
Валюту берите из данных (order["currency"], ctx.ggsel.currency()), никогда не пишите знак рубля в коде: у площадок и заказов разная валюта.
Показатели и графики
ui.stats([
ui.stat("Отправлено сегодня", 12, icon="send", delta=20, hint="к вчерашнему дню"),
ui.stat("Выручка", 45200, format="money", currency="RUB", icon="money"),
ui.stat("Правила работают", "3 из 4", icon="list-checks", page="rules"),
ui.stat("Ошибки", 2, icon="alert", tone="danger", page="history"),
])delta — изменение в процентах (бейдж «+20%» зелёный, «−5%» красный), page — открыть страницу по нажатию на карточку, format: number, money, percent. ui.stats(items, columns=) — число колонок, по умолчанию до четырёх.
ui.chart(data, kind=, label=, format=, currency=, height=, compare=):
kind | Данные |
|---|---|
"area", "line", "bar" | Ряд по датам: [{"date": "2026-09-01", "value": 120}, …]. compare — второй ряд пунктиром (прошлый период). |
"bars" | Горизонтальные полосы: [{"label": "Steam", "value": 12, "hint": "…"}, …]. |
"donut" | Доли: [{"label": "5★", "value": 70}, …]. |
Таблицы
Таблица в стиле shadcn/ui: сортировка по колонкам, поиск, фильтры, выбор строк, меню действий, страницы и выбор видимых колонок.
ui.table(
[ui.column("product", "Товар GGsel", subtitle="product_meta"),
ui.column("template", "Текст"),
ui.column("state", "Статус", format="status"),
ui.column("sent", "Отправлено", align="right", format="number")],
rows,
filters=[ui.filter("state", "Статус", [("on", "Работают"), ("off", "Выключены")], style="tabs")],
searchable=True, search_placeholder="Поиск по товару или тексту",
selectable=True,
bulk_actions=[ui.button("Включить", "bulk_toggle", payload={"on": True}, icon="play")],
row_click=ui.button("Настроить", page="rule"),
row_menu=[ui.button("Удалить", "delete_rule", icon="trash", danger=True, confirm="Удалить правило?")],
footer="4 правила · 3 работают",
)| Параметр | Что делает |
|---|---|
searchable, search_placeholder | Поиск по всем колонкам прямо в браузере. |
filters | Фильтры ui.filter по полям строк. style="tabs" — вкладки со счётчиками над таблицей, "select" — кнопка-фильтр рядом с поиском. Бейдж фильтруется по своему value. |
row_actions | Кнопки в конце строки. Кнопка с action получает строку в data["row"]; кнопка с page открывает страницу с params["id"]. |
row_menu | Пункты меню «⋯» строки — для второстепенных и опасных действий. |
row_click | Кнопка, которая срабатывает по нажатию на строку: обычно открывает карточку записи. |
selectable, bulk_actions | Галочки и действия над выбранными строками: data["ids"] и data["rows"]. |
row_key | Поле с идентификатором строки (по умолчанию id). |
footer, footer_actions | Итог и кнопки под таблицей. |
toolbar | Кнопки справа от поиска. |
column_toggle | Меню «Колонки». По умолчанию есть, если колонок больше четырёх. |
page_size | Строк на странице (5–200, по умолчанию 20). |
dense=True | Компактные строки. |
title, description, empty_text | Заголовок, пояснение и текст пустой таблицы. |
ui.column(key, label, align=, format=, width=, currency=, subtitle=, sortable=, hidden=, nowrap=):
format | Как показывается значение |
|---|---|
text | Текст (по умолчанию). |
money | Сумма: валюта из поля currency строки или параметра currency колонки. |
number, percent | Число с разрядами, проценты. |
date, datetime, relative | Дата, дата и время, «3 дн назад». Значение — ISO-строка. |
badge, status | Бейдж ui.badge; status — бейдж с точкой. |
link | Ссылка ui.link или https://-строка. |
code | Моноширинный текст: ключи, номера. |
image, avatar | Картинка; аватар с инициалами из текста. |
tags | Список строк бейджами. |
progress | Полоска прогресса, значение 0–100. |
bool | «Да» или «Нет». |
subtitle — поле строки для второй строки ячейки: «#4389201 · 0,45 ₽/шт.». hidden=True — колонка скрыта, пока пользователь не включит её в меню «Колонки».
Поиск и фильтры страницы
Фильтры таблицы работают в браузере с уже полученными строками. Если данных много и отбирать их должен плагин, используйте панель ui.toolbar: её поиск и фильтры меняют параметры страницы, и страница перерисовывается.
@app.page("history", "История", icon="history")
def history_page(ctx, params):
q = str(params.get("q") or "").lower()
status = str(params.get("status") or "")
items = [h for h in history(ctx) if (not status or h["status"] == status) and q in h["order_id"].lower()]
return [
ui.toolbar([
ui.search("q", placeholder="Номер заказа, товар или покупатель"),
ui.filter("status", "Статус", [("sent", "Отправлено"), ("error", "Ошибка")]),
ui.button("Очистить историю", "clear_history", variant="ghost", icon="trash", confirm="Очистить?"),
]),
ui.table(COLUMNS, rows_of(items), empty_text="Ничего не найдено."),
]ui.search(param="q", placeholder=) меняет параметр с небольшой задержкой при вводе. ui.filter(key, label, options, style=) в панели — кнопка-фильтр или вкладки (style="tabs"). Кнопки панели прижимаются вправо.
Варианты выбора в ui.filter и полях: "a", ("a", "Подпись") или {"value": "a", "label": "Подпись", "hint": "пояснение", "count": 3}.
Списки, ленты и этапы
| Блок | Назначение |
|---|---|
ui.keyvalue({...} или [(label, value)], columns=1) | «Параметр — значение». Значение — текст, ui.badge, ui.value, ui.link. |
ui.list_view([ui.item(...)], empty_text=) | Список: «Требуют внимания», связки, последние события. |
ui.timeline([ui.item(..., time=)], empty_text=) | Лента по времени: история заказа, журнал действий. |
ui.steps(["Оплачен", "Подтверждён", "Отзыв"], current=1) | Этапы процесса; current — номер текущего с нуля. |
ui.item(title, description=, badge=, tone=, meta=, action=, actions=, icon=, time=, image=) — элемент списка и ленты (image — миниатюра слева вместо иконки):
ui.list_view([
ui.item("Заказ #10842", "Покупатель закрыл чат", badge="ошибка", tone="danger", meta="Telegram · подписчики",
action=ui.button("Повторить", "retry", payload={"id": "e1"}, size="sm")),
], empty_text="Все сообщения отправлены.")Кнопки и действия
ui.button(label, action=, payload=, page=, params=, href=, dialog=, variant=, icon=, confirm=, danger=, disabled=, size=):
action— вызвать действие плагина@app.actionсpayload;page+params— открыть страницу плагина;href— внешняя ссылкаhttps://;dialog— открыть окноui.dialogбез обращения к плагину;confirm— текст подтверждения перед действием;variant:primary(цвет площадки),secondary,outline,ghost,link,soft,danger;size="sm"— маленькая кнопка.
| Блок | Назначение |
|---|---|
ui.actions([buttons], align=) | Ряд кнопок; align: start, end, center, between (первая слева, остальные справа). |
ui.menu(label, [buttons], icon=, variant=) | Кнопка с выпадающим списком действий. |
ui.toggle(key, label, value, action=, description=, confirm=) | Строка с переключателем: сразу вызывает действие с {"key": key, "value": True/False}. Если действие упало, переключатель вернётся назад. |
Действие получает data: payload кнопки, значения формы, строку таблицы (row) или выбранные строки (ids, rows). Что можно вернуть:
| Результат | Эффект |
|---|---|
None или "Текст" | Страница обновится; строка — уведомление «успешно». |
ui.result("Текст", tone="info") | Уведомление: success, info, warning, danger. |
ui.result(page="rule", params={"id": 5}) | Перейти на страницу плагина. |
ui.result(dialog=ui.dialog(...)) | Открыть окно. |
ui.result(operation=op) | Показать прогресс запущенной операции. |
ui.result(download=ctx.download(...)) | Скачать файл. |
ui.result(errors={"price": "…"}) | Ошибки у полей формы (то же, что raise ValidationError). |
ui.result(values={...}) | Новые значения полей формы или мастера. |
ui.result(close=True) | Закрыть окно, из которого вызвано действие. |
ui.result(refresh=False) | Не перерисовывать страницу. |
ui.dialog(title, blocks, description=, size=, side=) — окно: size — sm, md, lg, xl; side=True — панель справа (удобно для карточки записи поверх таблицы).
@app.action("show_message")
def show_message(ctx, data):
row = data.get("row") or {}
return ui.result(refresh=False, dialog=ui.dialog("Заказ " + row["order_id"], [
ui.keyvalue([("Покупатель", row["buyer"]), ("Статус", row["status"])]),
ui.chat([{"text": row["text"], "author": "Вы", "direction": "out", "time": "12:40"}]),
], side=True))Формы
ui.form("add_slot", [
ui.field.section("Товар"),
ui.field.text("title", "Название", required=True, width="half"),
ui.field.select("region", "Регион", [("ar", "Аргентина"), ("tr", "Турция")], default="ar", width="half"),
ui.field.radio("mode", "Режим", [{"value": "auto", "label": "Автоматически", "hint": "По расписанию"},
{"value": "manual", "label": "Вручную"}], default="auto"),
ui.field.number("markup", "Наценка", min=0, max=200, default=15, suffix="%",
show_if={"field": "mode", "equals": "auto"}),
ui.field.tags("stop_words", "Стоп-слова", placeholder="Слово и Enter"),
ui.field.file("keys", "Файл с ключами", accept=".txt", max_mb=5),
], submit="Добавить", action="add_slot",
secondary=[ui.button("Отмена", page="slots", variant="ghost")])| Поле | Значение в действии |
|---|---|
field.text(key, label, placeholder=, secret=, max_length=, prefix=, suffix=) | Строка. secret=True — пароль или ключ: не отдаётся в браузер. |
field.textarea(key, label, rows=, max_length=) | Многострочный текст. |
field.number(key, label, min=, max=, step=, prefix=, suffix=) | Число. |
field.switch(key, label) | True или False. |
field.select(key, label, options, searchable=, placeholder=) | Строка. searchable=True — список с поиском (сотни товаров). |
field.radio(key, label, options, cards=True) | Строка. Карточки с пояснением из hint варианта. |
field.checkboxes(key, label, options) | Список строк. |
field.tags(key, label) | Список фраз: ключевые слова, стоп-слова. |
field.template(key, label, variables=) | Текст сообщения с переменными и предпросмотром (см. «Сообщения покупателю»). |
field.date(key, label) | Дата ГГГГ-ММ-ДД. |
field.file(key, label, accept=, max_mb=) | Объект UploadedFile: name, size, mime, content, text(), lines(). |
field.hidden(key, value) | Скрытое значение, например id записи. |
field.section(title, description=) | Подзаголовок внутри длинной формы. |
Общие параметры полей: hint, placeholder, required, default, disabled, width="half" (два поля в ряд), show_if={"field": "mode", "equals": "manual"} или {"field": "mode", "in": ["a", "b"]} — показывать поле, только если другое поле равно значению. Скрытые условием поля не проверяются и не отправляются.
ui.form(id, fields, submit=, action=, values=, description=, danger=, secondary=, columns=2): values — текущие значения, secondary — кнопки слева от «Сохранить», columns=1 — все поля в одну колонку.
Lovsel проверяет обязательные поля и числа ещё до вызова действия и приводит значения к типам. Ошибку, которую видит только плагин, покажите у поля:
if price <= 0:
raise ValidationError({"price": "Цена должна быть больше нуля"})Не просите пользователя вводить данные по одному сообщению («введите название», «теперь цену»): одна форма — одно действие.
Мастер
Пошаговая форма для создания сложной записи: «1 · Товар → 2 · Сообщение → 3 · Проверка».
ui.wizard("new_rule", [
ui.step("product", "Товар", [
ui.field.select("product_id", "Товар GGsel", options, searchable=True, required=True),
], description="Какой товар подключить?"),
ui.step("message", "Сообщение", [
ui.field.template("template", "Текст благодарности", variables=VARIABLES, required=True),
ui.field.switch("enabled", "Включить сразу", default=True),
]),
ui.step("review", "Проверка", review=True, blocks=[ui.alert("Одно сообщение на заказ.", tone="info")]),
], action="create_rule", submit="Создать правило", validate="check_rule",
cancel=ui.button("Отмена", page="rules", variant="ghost"))- «Далее» проверяет обязательные поля текущего шага.
validate— действие, которое вызывается на каждом шаге сdata["__step"]= id шага. Оно можетraise ValidationError({...})или вернутьui.result(values={...}), чтобы заполнить следующие шаги.review=True— итоговый шаг: Lovsel сам покажет все введённые значения.- На последнем шаге вызывается
actionсо всеми значениями. Обычно он возвращаетui.result("Создано", page="rule", params={"id": new_id}).
Сообщения покупателю
Для плагинов, которые пишут покупателям, есть редактор шаблона и предпросмотр в стиле чата площадки.
VARIABLES = [
ui.var("buyer", "Имя покупателя", "Алексей"),
ui.var("product", "Название товара", "Telegram · подписчики"),
ui.var("order_id", "Номер заказа", "10842"),
]
ui.form("save_text", [
ui.field.template("text", "Текст сообщения", variables=VARIABLES, required=True),
], values={"text": current_text}, submit="Сохранить текст")ui.var(key, label, example)— переменная:{buyer}в тексте, «Алексей» в предпросмотре.field.template(key, label, variables=, rows=, preview=True, preview_title=)— поле с чипами переменных (вставляются туда, где стоит курсор) и живым предпросмотром рядом.ui.message_preview(text, variables=, title=, author=, note=, marketplace=)— готовое сообщение глазами покупателя.ui.chat(messages, title=, marketplace=)— переписка:[{"text": "…", "author": "Покупатель", "direction": "in", "time": "12:40"}]. Исходящие сообщения ("out") окрашены в цвет площадки.
Подставлять переменные в настоящий текст плагин должен сам (text.replace("{buyer}", name)): Lovsel лишь показывает пример.
Прогресс, операции и журнал
| Блок | Назначение |
|---|---|
ui.progress(value, max, label=) | Статичный прогресс: «14 из 50». |
ui.progress(operation=op.id) | Живой прогресс запущенной операции. |
ui.operations(limit=10) | Все операции плагина с прогрессом и кнопкой «Остановить». |
ui.logs(limit=100, filters=True) | Журнал плагина (ctx.log) с поиском и фильтром по уровню. |
Ошибки обработчиков Lovsel записывает в журнал сам — с трассировкой до строки вашего файла. Автор видит её во вкладке «Журнал» и в кабинете разработчика.
Иконки
Иконки задаются именем: activity, alert, arrow-left, arrow-right, bar-chart, bell, book, bot, box, calendar, card, cart, chart, check, check-circle, clock, copy, dashboard, download, edit, external, eye, file, file-text, filter, gift, globe, hash, heart, help, history, image, inbox, info, key, layers, layout-dashboard, link, list, list-checks, lock, mail, message, message-square, messages, money, more, order, package, panel, pause, percent, play, plus, receipt, refresh, rocket, search, send, settings, shield, sliders, sparkles, star, stop, store, table, tag, trash, trend, truck, upload, user, users, wallet, wand, x, x-circle, zap. Можно указать эмодзи.
Проверка страниц
Тестовый стенд отрисовывает каждую страницу и проверяет блоки: неизвестный тип блока, кнопку без действия, повторяющийся ключ поля или картинку с неверным адресом он покажет с путём до места ошибки. Если передать папку с картинками плагина (assets="images/"), стенд проверит, что каждая ui.asset("имя") ссылается на существующий файл.
python -m lovsel_sdk.testing my_plugin.pyfrom lovsel_sdk.testing import load_plugin
plugin = load_plugin("my_plugin.py", products=[{"id": 101, "title": "Ключ Steam", "price": 199}],
assets="images/")
blocks = plugin.page("rules") # ValueError, если блок описан с ошибкой
plugin.action("create_rule", {"product_id": "101", "template": "Спасибо!", "enabled": True})
plugin.emit("new_order", {"order": {"id": "9001", "product_id": 101, "chat_id": 9001}})
assert plugin.ggsel_sent[0]["text"] == "Спасибо!"Готовые образцы
Список и карточка записи. Страница со списком — таблица с row_click=ui.button("Открыть", page="item"). Скрытая страница @app.page("item", ..., hidden=True) читает params["id"], начинается с ui.header(..., back=ui.button("Все записи", page="items")) и показывает ui.keyvalue, историю ui.timeline и опасные действия в ui.details.
Создание записи. Кнопка «Новая запись» в шапке списка ведёт на скрытую страницу с ui.wizard. Действие мастера возвращает ui.result("Создано", page="item", params={"id": new_id}).
Сценарии во вкладках. ui.tabs с вкладкой на сценарий: в каждой ui.toggle «Включено» и форма с field.template. Так устроены приветствие, просьба об отзыве и ответ на отзыв в примере для FunPay.
Журнал с поиском. ui.toolbar([ui.search(...), ui.filter(...)]) и таблица, которую плагин отбирает по params; нажатие на строку открывает ui.dialog(..., side=True) с подробностями.
Обзор. ui.header с главной кнопкой, ui.stats на четыре показателя, ui.grid с «Требуют внимания» и графиком, последние события в ui.timeline.
Полные примеры
- GGsel — «Благодарности покупателям»: меню слева, правила с мастером, шаблон с предпросмотром, история с поиском и повторной отправкой. Файл
docs/examples/ggsel_thanks.py, разбор — в статье «Плагины для GGsel». - FunPay — «Отзывы и напоминания»: сценарии во вкладках, заказы с этапами, отзывы с фильтрами, переписка в окне сбоку. Файл
docs/examples/funpay_reviews.py, разбор — в статье «Плагины для FunPay».
Оба примера можно скачать кнопкой «Пример плагина» в статье площадки, загрузить в «Плагины → Разработчикам» и открыть у себя — без публикации в магазине.