Упрощенный китайский | Английский
Библиотека расширения для Nicegui. Встроенные отзывчивые компоненты, полностью реализуя программирование интерфейса с адаптивным интерфейсом.
Смотрите больше примеров
Заголовок - официальная реализация Flash Sale, библиотека интерфейсов Python, Nicegui с удаленным кодом события 90%
Официальная учетная запись WeChat - официальная реализация Flash Sale, библиотека интерфейсов Python, Nicegui с удаленным кодом события 90%
pip install ex4nicegui -U
Мы начинаем с простого приложения для встречного приложения, где пользователи могут увеличить или уменьшить количество, нажав кнопку.
Вот полный код:
from nicegui import ui
from ex4nicegui import rxui
# 数据状态代码
class Counter ( rxui . ViewModel ):
count : int = 0
def increment ( self ):
self . count += 1
def decrement ( self ):
self . count -= 1
# 界面代码
counter = Counter ()
with ui . row ( align_items = "center" ):
ui . button ( icon = "remove" , on_click = counter . decrement )
rxui . label ( counter . count )
ui . button ( icon = "add" , on_click = counter . increment )
ui . run () Смотрите больше деталей сейчас. ex4nicegui следует методу, управляемому данными для определения интерфейса. Данные о состоянии определяют все данные, которые могут быть изменены в приложении.
Ниже приведено определение данных о состоянии Counter :
class Counter ( rxui . ViewModel ):
count : int = 0rxui.ViewModelcount переменных, которое представляет текущее значение счетчика, с начальным значением 0.Далее, определите серию методов манипулирования данными в классе:
def increment ( self ):
self . count += 1
def decrement ( self ):
self . count -= 1count Затем в коде интерфейса создайте экземпляр Counter объекта.
counter = Counter () Мы связываем переменную count через компонент rxui.label . Приведите метод манипулирования данными с событием нажатия кнопки.
ui . button ( icon = "remove" , on_click = counter . decrement )
rxui . label ( counter . count )
ui . button ( icon = "add" , on_click = counter . increment )label в пространстве имен rxui , а не компонент label в пространстве имен nicegui .rxui.label связывает переменную counter.count , и когда counter.count изменяется, компонент rxui.label автоматически обновляется.ui.button связывает counter.decrement Методы распределения и counter.increment и вызывает соответствующий метод при нажатии на кнопку.В сложных проектах
Counter-определенный код может быть размещен в отдельных модулях, а затем импортирован в код интерфейса.
Обратите внимание, что когда имя переменной класса подчеркнуто, состояние данных не будет автоматически обновляться.
class Counter ( rxui . ViewModel ):
count : int = 0 # 响应式数据,能自动同步界面
_count : int = 0 # 这里的下划线表示私有变量,不会自动同步界面Рядом с предыдущим примером мы добавляем другую функцию. Когда значение счетчика меньше 0, шрифт отображается красным, когда он больше 0, он отображается зеленым, в противном случае он отображается в черном.
# 数据状态代码
class Counter ( rxui . ViewModel ):
count : int = 0
def text_color ( self ):
if self . count > 0 :
return "green"
elif self . count < 0 :
return "red"
else :
return "black"
def increment ( self ):
self . count += 1
def decrement ( self ):
self . count -= 1
# 界面代码
counter = Counter ()
with ui . row ( align_items = "center" ):
ui . button ( icon = "remove" , on_click = counter . decrement )
rxui . label ( counter . count ). bind_color ( counter . text_color )
ui . button ( icon = "add" , on_click = counter . increment )Значение цвета рассчитывается на основе текущего значения счетчика. Это принадлежит вторичному расчету. Просто определите обычные функции экземпляра.
def text_color ( self ):
if self . count > 0 :
return "green"
elif self . count < 0 :
return "red"
else :
return "black" Затем метод text_color связан с помощью метода bind_color компонента rxui.label , так что значение цвета автоматически обновляется.
rxui . label ( counter . count ). bind_color ( counter . text_color )Теперь мы используем текст под счетчиком, чтобы отобразить значение текста цветного текста текущего счетчика.
...
# 数据状态代码
class Counter ( rxui . ViewModel ):
...
# 界面代码
counter = Counter ()
with ui . row ( align_items = "center" ):
ui . button ( icon = "remove" , on_click = counter . decrement )
rxui . label ( counter . count ). bind_color ( counter . text_color )
ui . button ( icon = "add" , on_click = counter . increment )
rxui . label ( lambda : f"当前计数器值为 { counter . count } , 颜色值为 { counter . text_color () } " ) В приведенном выше коде есть два места, которые используют метод counter.text_color . Когда counter.count изменяется, counter.text_color выполняет два вычисления. Второй расчет избыточный.
Чтобы избежать ненужных вычислений, мы можем кэшировать counter.text_color .
# 数据状态代码
class Counter ( rxui . ViewModel ):
count : int = 0
@ rxui . cached_var
def text_color ( self ):
if self . count > 0 :
return "green"
elif self . count < 0 :
return "red"
else :
return "black"rxui.cached_var может кэшировать результаты функции, чтобы избежать ненужных расчетов.В следующем примере показано, как использовать список.
class AppState ( rxui . ViewModel ):
nums = []
# nums = [1,2,3] 如果需要初始化,必须在 __init__ 中设置
def __init__ ( self ):
super (). __init__ ()
self . nums = [ 1 , 2 , 3 ]
def append ( self ):
new_num = max ( self . nums ) + 1
self . nums . append ( new_num )
def pop ( self ):
self . nums . pop ()
def reverse ( self ):
self . nums . reverse ()
def display_nums ( self ):
return ", " . join ( map ( str , self . nums ))
# 界面代码
state = AppState ()
with ui . row ( align_items = "center" ):
ui . button ( "append" , on_click = state . append )
ui . button ( "pop" , on_click = state . pop )
ui . button ( "reverse" , on_click = state . reverse )
rxui . label ( state . display_nums ) Если вам нужно инициализировать список при определении списка, рекомендуется установить его в __init__ .
class AppState ( rxui . ViewModel ):
nums = []
# nums = [1,2,3] 如果需要初始化,必须在 __init__ 中设置
def __init__ ( self ):
super (). __init__ ()
self . nums = [ 1 , 2 , 3 ]
... Другой способ - использовать rxui.list_var
class AppState ( rxui . ViewModel ):
# nums = []
# nums = [1,2,3] 如果需要初始化,必须在 __init__ 中设置
nums = rxui . list_var ( lambda : [ 1 , 2 , 3 ])
...rxui.list_var - это функция, которая возвращает список После определения списка мы можем использовать effect_refreshable.on Decorator для отображения данных списка в интерфейсе.
В следующем примере интерфейс будет динамически отображать значки, выбранные в раскрывающемся поле.
from ex4nicegui import rxui , effect_refreshable
class AppState ( rxui . ViewModel ):
icons = []
_option_icons = [ "font_download" , "warning" , "format_size" , "print" ]
state = AppState ()
# 界面代码
with ui . row ( align_items = "center" ):
@ effect_refreshable . on ( state . icons )
def _ ():
for icon in state . icons :
ui . icon ( icon , size = "2rem" )
rxui . select ( state . _option_icons , value = state . icons , multiple = True ) Среди них @effect_refreshable.on(state.icons) явно указывает зависимость. При изменении state.icons , функция _ повторно предъявляет.
@ effect_refreshable . on ( state . icons )
def _ ():
# 这里的代码会在 state.icons 变化时重新执行
...Обратите внимание, что каждый раз, когда вы выполняете, контент внутри будет очищен. Это версия
ui.refreshable, управляемая данными.
В принципе, контролируемые данные могут автоматически контролироваться без указания .on .
@ effect_refreshable # 没有使用 .on(state.icons)
def _ ():
# 这里读取了 state.icons,因此会自动监控
for icon in state . icons :
ui . icon ( icon , size = "2rem" )Рекомендуется всегда указывать зависимости через
.onВо избежание неожиданных обновлений
ViewModel использует прокси -объекты для создания адаптивных данных. Когда необходимо сохранить данные, вы можете использовать rxui.ViewModel.to_value для преобразования их в обычные данные.
В следующем примере нажатие кнопки отобразит словарь данных о состоянии MY_APP.
from nicegui import ui
from ex4nicegui import rxui
class MyApp ( rxui . ViewModel ):
a = 0
sign = "+"
b = 0
def show_data ( self ):
# >> {"a": 0, "sign": '+, "b": 0}
return rxui . ViewModel . to_value ( self )
def show_a ( self ):
# >> 0
return rxui . ViewModel . to_value ( self . a )
my_app = MyApp ()
rxui . number ( value = my_app . a , min = 0 , max = 10 )
rxui . radio ([ "+" , "-" , "*" , "/" ], value = my_app . sign )
rxui . number ( value = my_app . b , min = 0 , max = 10 )
ui . button ( "show data" , on_click = lambda : ui . notify ( my_app . show_data ())) В сочетании с rxui.ViewModel.on_refs_changed данные могут быть автоматически сохранены в локальной области при изменении данных.
from nicegui import ui
from ex4nicegui import rxui
from pathlib import Path
import json
class MyApp ( rxui . ViewModel ):
a = 0
sign = "+"
b = 0
_json_path = Path ( __file__ ). parent / "data.json"
def __init__ ( self ):
super (). __init__ ()
@ rxui . ViewModel . on_refs_changed ( self )
def _ ():
# a, sign, b 任意一个值变化时,自动保存到本地
self . _json_path . write_text ( json . dumps ( self . show_data ()))
def show_data ( self ):
return rxui . ViewModel . to_value ( self )
...to_refdeep_refeffectref_computedasync_computedonnew_scopebi.data_source В v0.7.0 класс ViewModel был введен для управления набором адаптивных данных.
Вот простой пример калькулятора:
from ex4nicegui import rxui
class Calculator ( rxui . ViewModel ):
num1 = 0
sign = "+"
num2 = 0
@ rxui . cached_var
def result ( self ):
# 当 num1,sign,num2 任意一个值发生变化时,result 也会重新计算
return eval ( f" { self . num1 } { self . sign } { self . num2 } " )
# 每个对象拥有独立的数据
calc = Calculator ()
with ui . row ( align_items = "center" ):
rxui . number ( value = calc . num1 , label = "Number 1" )
rxui . select ( value = calc . sign , options = [ "+" , "-" , "*" , "/" ], label = "Sign" )
rxui . number ( value = calc . num2 , label = "Number 2" )
ui . label ( "=" )
rxui . label ( calc . result ). bind_color (
lambda : "red" if calc . result () < 0 else "black"
) В следующем примере каждый человек использует карту для отображения. Верхне показывает средний возраст всех людей. Когда человек старше среднего возраста, внешняя граница карты будет краснеть. Измените возраст через number компонент, и все будет автоматически обновлено.
from typing import List
from ex4nicegui import rxui
from itertools import count
from nicegui import ui
id_generator = count ()
class Person ( rxui . ViewModel ):
name = ""
age = 0
def __init__ ( self , name : str , age : int ):
super (). __init__ ()
self . name = name
self . age = age
self . id = next ( id_generator )
class Home ( rxui . ViewModel ):
persons : List [ Person ] = []
deleted_person_index = 0
@ rxui . cached_var
def avg_age ( self ) -> float :
if len ( self . persons ) == 0 :
return 0
return round ( sum ( p . age for p in self . persons ) / len ( self . persons ), 2 )
def avg_name_length ( self ):
if len ( self . persons ) == 0 :
return 0
return round ( sum ( len ( p . name ) for p in self . persons ) / len ( self . persons ), 2 )
def delete_person ( self ):
if self . deleted_person_index < len ( self . persons ):
del self . persons [ int ( self . deleted_person_index )]
def sample_data ( self ):
self . persons = [
Person ( "alice" , 25 ),
Person ( "bob" , 30 ),
Person ( "charlie" , 31 ),
Person ( "dave" , 22 ),
Person ( "eve" , 26 ),
Person ( "frank" , 29 ),
]
home = Home ()
home . sample_data ()
rxui . label ( lambda : f"平均年龄: { home . avg_age () } " )
rxui . label ( lambda : f"平均名字长度: { home . avg_name_length () } " )
rxui . number (
value = home . deleted_person_index , min = 0 , max = lambda : len ( home . persons ) - 1 , step = 1
)
ui . button ( "删除" , on_click = home . delete_person )
with ui . row ():
@ rxui . vfor ( home . persons , key = "id" )
def _ ( store : rxui . VforStore [ Person ]):
person = store . get_item ()
with rxui . card (). classes ( "outline" ). bind_classes (
{
"outline-red-500" : lambda : person . age > home . avg_age (),
}
):
rxui . input ( value = person . name , placeholder = "名字" )
rxui . number ( value = person . age , min = 1 , max = 100 , step = 1 , placeholder = "年龄" )
ui . run () Если вы думаете, что код rxui.vfor слишком сложный, вместо этого вы можете использовать декоратор effect_refreshable .
from ex4nicegui import rxui , Ref , effect_refreshable
...
# 明确指定监控 home.persons 变化,可以避免意外刷新
@ effect_refreshable . on ( home . persons )
def _ ():
for person in home . persons . value :
...
rxui . number ( value = person . age , min = 1 , max = 100 , step = 1 , placeholder = "年龄" )
... Следует отметить, что всякий раз, когда home.persons Перечисляют изменения (например, добавление или удаление элементов), функция, украшенная effect_refreshable будет повторно подключена. То есть все элементы будут воссозданы.
Для более сложных приложений вы можете просмотреть примеры
from ex4nicegui import (
to_ref ,
ref_computed ,
on ,
effect ,
effect_refreshable ,
batch ,
event_batch ,
deep_ref ,
async_computed
) Обычно используется to_ref , deep_ref , effect , ref_computed , on , async_computed
to_ref Определить адаптивные объекты, читать и записать .value
a = to_ref ( 1 )
b = to_ref ( "text" )
a . value = 2
b . value = 'new text'
print ( a . value )Когда значение является сложным объектом, вложенный объект не будет реагировать по умолчанию.
a = to_ref ([ 1 , 2 ])
@ effect
def _ ():
print ( 'len:' , len ( a . value ))
# 不会触发 effect
a . value . append ( 10 )
# 整个替换则会触发
a . value = [ 1 , 2 , 10 ] Когда параметр is_deep установлен на True , можно получить возможность глубокого ответа.
a = to_ref ([ 1 , 2 ], is_deep = True )
@ effect
def _ ():
print ( 'len:' , len ( a . value ))
# print 3
a . value . append ( 10 )
deep_refэквивалентенto_ref, когдаis_deepустановлен наTrue
deep_ref Эквивалентно to_ref , когда is_deep установлен на True .
Особенно полезно, когда источник данных является списком, словарем или пользовательским классом. Объекты, полученные через .value
data = [ 1 , 2 , 3 ]
data_ref = deep_ref ( data )
assert data_ref . value is not data Исходный объект можно получить через to_raw
from ex4nicegui import to_raw , deep_ref
data = [ 1 , 2 , 3 ]
data_ref = deep_ref ( data )
assert data_ref . value is not data
assert to_raw ( data_ref . value ) is data effectПринять функцию и автоматически отслеживать изменения в адаптивном объекте, используемых в функции, автоматически выполняя функцию
a = to_ref ( 1 )
b = to_ref ( "text" )
@ effect
def auto_run_when_ref_value ():
print ( f"a: { a . value } " )
def change_value ():
a . value = 2
b . value = "new text"
ui . button ( "change" , on_click = change_value ) В первый раз, когда эффект выполняется, функция auto_run_when_ref_value будет выполнена один раз. После этого нажмите кнопку и измените значение a (через a.value ), и функция auto_run_when_ref_value будет выполнена снова.
Не рассеивайте большой объем логики обработки данных в
onилиeffect. Большаяonилиeffectдолжна быть логикой работы интерфейса, а не отзывчивой логикой обработки данных.
ref_computed С той же функцией, что и effect , ref_computed также может вернуть результат из функции. Обычно используется для выполнения квадратичных расчетов от to_ref
a = to_ref ( 1 )
a_square = ref_computed ( lambda : a . value * 2 )
@ effect
def effect1 ():
print ( f"a_square: { a_square . value } " )
def change_value ():
a . value = 2
ui . button ( "change" , on_click = change_value ) После a_square кнопки изменение значения a.value Поскольку значение a_square читается в effect1 , выполнение effect1 запускается.
ref_computedтолько для чтенияto_ref
Начиная с v0.7.0 , не рекомендуется использовать метод экземпляра приложения ref_computed . Вы можете использовать rxui.ViewModel и использовать rxui.cached_var decorator
class MyState ( rxui . ViewModel ):
def __init__ ( self ) -> None :
self . r_text = to_ref ( "" )
@ rxui . cached_var
def post_text ( self ):
return self . r_text . value + "post"
state = MyState ()
rxui . input ( value = state . r_text )
rxui . label ( state . post_text )async_computed При использовании асинхронных функций во вторичных расчетах используйте async_computed
# 模拟长时间执行的异步函数
async def long_time_query ( input : str ):
await asyncio . sleep ( 2 )
num = random . randint ( 20 , 100 )
return f"query result[ { input = } ]: { num = } "
search = to_ref ( "" )
evaluating = to_ref ( False )
@ async_computed ( search , evaluating = evaluating , init = "" )
async def search_result ():
return await long_time_query ( search . value )
rxui . lazy_input ( value = search )
rxui . label (
lambda : "查询中" if evaluating . value else "上方输入框输入内容并回车搜索"
)
rxui . label ( search_result )async_computed должен явно указывать адаптивные данные, которые необходимо контролировать. Используйте списки для указания нескольких адаптивных данных одновременно.evaluating параметров - это адаптивные данные типа Bool. Когда асинхронная функция выполняется, значение этой переменной является True , и она False после завершения расчета.init указывает начальный результат on effect , но on необходимости указать объект, отвечающий на мониторинг
a1 = to_ref ( 1 )
a2 = to_ref ( 10 )
b = to_ref ( "text" )
@ on ( a1 )
def watch_a1_only ():
print ( f"watch_a1_only ... a1: { a1 . value } ,a2: { a2 . value } " )
@ on ([ a1 , b ], onchanges = True )
def watch_a1_and_b ():
print ( f"watch_a1_and_b ... a1: { a1 . value } ,a2: { a2 . value } ,b: { b . value } " )
def change_a1 ():
a1 . value += 1
ui . notify ( "change_a1" )
ui . button ( "change a1" , on_click = change_a1 )
def change_a2 ():
a2 . value += 1
ui . notify ( "change_a2" )
ui . button ( "change a2" , on_click = change_a2 )
def change_b ():
b . value += "x"
ui . notify ( "change_b" )
ui . button ( "change b" , on_click = change_b )onchanges является истинной (значение по умолчанию является false), указанная функция не будет выполнена во время привязки.Не рассеивайте большой объем логики обработки данных в
onилиeffect. Большаяonилиeffectдолжна быть логикой работы интерфейса, а не отзывчивой логикой обработки данных.
new_scope По умолчанию все функции обнаружения автоматически разрушаются, когда клиентское соединение отключено. Если требуется более мелкозернистый контроль, вы можете использовать new_scope
from nicegui import ui
from ex4nicegui import rxui , to_ref , effect , new_scope
a = to_ref ( 0.0 )
scope1 = new_scope ()
@ scope1 . run
def _ ():
@ effect
def _ ():
print ( f"scope 1: { a . value } " )
rxui . number ( value = a )
rxui . button ( "dispose scope 1" , on_click = scope1 . dispose )Создайте двустороннюю привязку на входном элементе или компоненте формы.
ref простых типов значений поддерживают двустороннюю привязку по умолчанию
from ex4nicegui import rxui , to_ref , deep_ref
data = to_ref ( "init" )
rxui . label ( lambda : f" { data . value = } " )
# 默认就是双向绑定
rxui . input ( value = data )str , int При использовании сложных структур данных используется deep_ref для поддержания отзывчивости вложенных значений
data = deep_ref ({ "a" : 1 , "b" : [ 1 , 2 , 3 , 4 ]})
rxui . label ( lambda : f" { data . value = !s } " )
# 当前版本没有任何绑定效果.或许未来的版本可以解决
rxui . input ( value = data . value [ "a" ])
# 只读绑定.其他途径修改了 `data.value["a"]` ,此输入框会同步,但反过来不行
rxui . input ( value = lambda : data . value [ "a" ])
# 要使用 vmodel 才能双向绑定
rxui . input ( value = rxui . vmodel ( data , "a" ))
# 也可以直接使用,但不推荐
rxui . input ( value = rxui . vmodel ( data . value [ 'a' ]))rxui.input(value=1)rxui.vmodel , может достичь двусторонней привязкиЕсли вы используете
rxui.ViewModel, вам, вероятно, не нужно использоватьvmodel
Вы можете обратиться к делу списка TODO
Список рендеринга на основе ответных данных списка. Каждый компонент обновляется по спросу. Элементы данных поддерживают словарь или объекты любого типа.
Начиная с v0.7.0 , рекомендуется использовать его с rxui.ViewModel . В отличие от использования декоратора effect_refreshable , vfor не воссоздает все элементы, а обновляет существующие элементы.
Ниже приведен пример сортировки карт, карты всегда отсортируются по возрасту. Когда вы изменяете возрастные данные в карте, карты будут корректировать заказ в режиме реального времени. Тем не менее, фокус курсора не покидает поле ввода.
from typing import List
from nicegui import ui
from ex4nicegui import rxui , deep_ref as ref , Ref
class Person ( rxui . ViewModel ):
def __init__ ( self , name : str , age : int ) -> None :
self . name = name
self . age = ref ( age )
class MyApp ( rxui . ViewModel ):
persons : Ref [ List [ Person ]] = rxui . var ( lambda : [])
order = rxui . var ( "asc" )
def sort_by_age ( self ):
return sorted (
self . persons . value ,
key = lambda p : p . age . value ,
reverse = self . order . value == "desc" ,
)
@ staticmethod
def create ():
persons = [
Person ( name = "Alice" , age = 25 ),
Person ( name = "Bob" , age = 30 ),
Person ( name = "Charlie" , age = 20 ),
Person ( name = "Dave" , age = 35 ),
Person ( name = "Eve" , age = 28 ),
]
app = MyApp ()
app . persons . value = persons
return app
# ui
app = MyApp . create ()
with rxui . tabs ( app . order ):
rxui . tab ( "asc" , "Ascending" )
rxui . tab ( "desc" , "Descending" )
@ rxui . vfor ( app . sort_by_age , key = "name" )
def each_person ( s : rxui . VforStore [ Person ]):
person = s . get_item ()
with ui . card (), ui . row ( align_items = "center" ):
rxui . label ( person . name )
rxui . number ( value = person . age , step = 1 , min = 0 , max = 100 )rxui.vfor decorator для индивидуальной функцииapp.sort_by_agekey параметров: чтобы отслеживать идентичность каждого узла, тем самым повторно используя и переупорядочивая существующие элементы, вы можете предоставить уникальный ключ для соответствующего блока каждого элемента. Индекс элементов списка используется по умолчанию. В примере, имя каждого считается уникальным.store.get_item . Поскольку сам человек наследует от rxui.ViewModel , его различные свойства могут быть непосредственно связаны с компонентом. Все классы компонентов предоставляют bind_classes для class привязки, поддерживая три различные структуры данных.
Словарь привязки
bg_color = to_ref ( False )
has_error = to_ref ( False )
rxui . label ( "test" ). bind_classes ({ "bg-blue" : bg_color , "text-red" : has_error })
rxui . switch ( "bg_color" , value = bg_color )
rxui . switch ( "has_error" , value = has_error ) Значение ключа словаря - это имя класса, а соответствующее значение является отзывчивой переменной с Bool. Когда отзывчивое значение True , имя класса применяется к классу компонентов
Привязывать адаптивные переменные с возвратом значения в качестве словаря
bg_color = to_ref ( False )
has_error = to_ref ( False )
class_obj = ref_computed (
lambda : { "bg-blue" : bg_color . value , "text-red" : has_error . value }
)
rxui . switch ( "bg_color" , value = bg_color )
rxui . switch ( "has_error" , value = has_error )
rxui . label ( "bind to ref_computed" ). bind_classes ( class_obj )
# or direct function passing
rxui . label ( "bind to ref_computed" ). bind_classes (
lambda : { "bg-blue" : bg_color . value , "text-red" : has_error . value }
)Отзывчивые переменные, связанные с перечислением или одной строкой
bg_color = to_ref ( "red" )
bg_color_class = ref_computed ( lambda : f"bg- { bg_color . value } " )
text_color = to_ref ( "green" )
text_color_class = ref_computed ( lambda : f"text- { text_color . value } " )
rxui . select ([ "red" , "green" , "yellow" ], label = "bg color" , value = bg_color )
rxui . select ([ "red" , "green" , "yellow" ], label = "text color" , value = text_color )
rxui . label ( "binding to arrays" ). bind_classes ([ bg_color_class , text_color_class ])
rxui . label ( "binding to single string" ). bind_classes ( bg_color_class ) from nicegui import ui
from ex4nicegui . reactive import rxui
from ex4nicegui . utils . signals import to_ref
bg_color = to_ref ( "blue" )
text_color = to_ref ( "red" )
rxui . label ( "test" ). bind_style (
{
"background-color" : bg_color ,
"color" : text_color ,
}
)
rxui . select ([ "blue" , "green" , "yellow" ], label = "bg color" , value = bg_color )
rxui . select ([ "red" , "green" , "yellow" ], label = "text color" , value = text_color ) bind_style передвигается в словаре, key - это имя стиля, value - значение стиля и адаптивная строка
Свяжите один атрибут
label = to_ref ( "hello" )
rxui . button ( "" ). bind_prop ( "label" , label )
# 允许使用函数
rxui . button ( "" ). bind_prop (
"label" , lambda : f" { label . value } world"
)
rxui . input ( value = label )Используйте Echarts для создания диаграмм
from nicegui import ui
from ex4nicegui import ref_computed , effect , to_ref
from ex4nicegui . reactive import rxui
r_input = to_ref ( "" )
# ref_computed 创建只读响应式变量
# 函数中使用任意其他响应式变量,会自动关联
@ ref_computed
def cp_echarts_opts ():
return {
"title" : { "text" : r_input . value }, #字典中使用任意响应式变量,通过 .value 获取值
"xAxis" : {
"type" : "category" ,
"data" : [ "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" , "Sun" ],
},
"yAxis" : { "type" : "value" },
"series" : [
{
"data" : [ 120 , 200 , 150 , 80 , 70 , 110 , 130 ],
"type" : "bar" ,
"showBackground" : True ,
"backgroundStyle" : { "color" : "rgba(180, 180, 180, 0.2)" },
}
],
}
input = rxui . input ( "输入内容,图表标题会同步" , value = r_input )
# 通过响应式组件对象的 element 属性,获取原生 nicegui 组件对象
input . element . classes ( "w-full" )
rxui . echarts ( cp_echarts_opts )
ui . run ()
Используйте параметр функции on event_name и query , чтобы просмотреть событие Echarts Event Chinese Document
Следующий пример связывает событие щелчка мыши
from nicegui import ui
from ex4nicegui . reactive import rxui
opts = {
"xAxis" : { "type" : "value" , "boundaryGap" : [ 0 , 0.01 ]},
"yAxis" : {
"type" : "category" ,
"data" : [ "Brazil" , "Indonesia" , "USA" , "India" , "China" , "World" ],
},
"series" : [
{
"name" : "first" ,
"type" : "bar" ,
"data" : [ 18203 , 23489 , 29034 , 104970 , 131744 , 630230 ],
},
{
"name" : "second" ,
"type" : "bar" ,
"data" : [ 19325 , 23438 , 31000 , 121594 , 134141 , 681807 ],
},
],
}
bar = rxui . echarts ( opts )
def on_click ( e : rxui . echarts . EChartsMouseEventArguments ):
ui . notify ( f"on_click: { e . seriesName } : { e . name } : { e . value } " )
bar . on ( "click" , on_click )Следующий пример запускает событие свисания мыши только для указанной серии
from nicegui import ui
from ex4nicegui . reactive import rxui
opts = {
"xAxis" : { "type" : "value" , "boundaryGap" : [ 0 , 0.01 ]},
"yAxis" : {
"type" : "category" ,
"data" : [ "Brazil" , "Indonesia" , "USA" , "India" , "China" , "World" ],
},
"series" : [
{
"name" : "first" ,
"type" : "bar" ,
"data" : [ 18203 , 23489 , 29034 , 104970 , 131744 , 630230 ],
},
{
"name" : "second" ,
"type" : "bar" ,
"data" : [ 19325 , 23438 , 31000 , 121594 , 134141 , 681807 ],
},
],
}
bar = rxui . echarts ( opts )
def on_first_series_mouseover ( e : rxui . echarts . EChartsMouseEventArguments ):
ui . notify ( f"on_first_series_mouseover: { e . seriesName } : { e . name } : { e . value } " )
bar . on ( "mouseover" , on_first_series_mouseover , query = { "seriesName" : "first" })
ui . run ()Создать эхарт из кода JavaScript
from pathlib import Path
rxui . echarts . from_javascript ( Path ( "code.js" ))
# or
rxui . echarts . from_javascript (
"""
(myChart) => {
option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar'
}
]
};
myChart.setOption(option);
}
"""
)setOption в функции. Функция также имеет второй параметр, который является глобальным объектом echarts , вы можете зарегистрировать карту через echarts.registerMap .
rxui . echarts . from_javascript (
"""
(chart,echarts) =>{
fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json')
.then(response => response.json())
.then(data => {
echarts.registerMap('test_map', data);
chart.setOption({
geo: {
map: 'test_map',
roam: true,
},
tooltip: {},
legend: {},
series: [],
});
});
}
"""
)Зарегистрировать карту.
rxui . echarts . register_map (
"china" , "https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json"
)
rxui . echarts (
{
"geo" : {
"map" : "china" ,
"roam" : True ,
},
"tooltip" : {},
"legend" : {},
"series" : [],
}
)map_name - это настраиваемое имя карты. Обратите внимание, что map должна быть зарегистрирована в конфигурации диаграммыsrc - это действительная ссылка на сеть данных карты. Если это данные SVG, вам нужно установить параметр type="svg"
rxui . echarts . register_map ( "svg-rect" , "/test/svg" , type = "svg" )Вы также можете напрямую предоставить объект пути файла JSON (путь) локальной карты данных
from pathlib import Path
rxui . echarts . register_map (
"china" , Path ( "map-data.json" )
) По сравнению с nicegui.ui.tab_panels , rxui.tab_panels не имеет tabs параметров. В соответствии с механизмом реагирования на данные связь между tabs и tab_panels требует только value параметра.
from nicegui import ui
from ex4nicegui import rxui , to_ref
names = [ "Tab 1" , "Tab 2" , "Tab 3" ]
current_tab = to_ref ( names [ 0 ])
with rxui . tabs ( current_tab ):
for name in names :
rxui . tab ( name )
with rxui . tab_panels ( current_tab ):
for name in names :
with rxui . tab_panel ( name ):
ui . label ( f"Content of { name } " ) Это связано с тем, что в соответствии с механизмом отклика данных связь компонентов достигается посредством промежуточного уровня данных ( to_ref ). Следовательно, tab_panels можно связать с другими компонентами (просто обязательно используйте один и тот же объект ref )
names = [ "Tab 1" , "Tab 2" , "Tab 3" ]
current_tab = to_ref ( names [ 0 ])
with rxui . tab_panels ( current_tab ):
for name in names :
with rxui . tab_panel ( name ):
ui . label ( f"Content of { name } " )
# tabs 不必在 panels 前面
with rxui . tabs ( current_tab ):
for name in names :
rxui . tab ( name )
rxui . select ( names , value = current_tab )
rxui . radio ( names , value = current_tab ). props ( "inline" )
rxui . label ( lambda : f"当前 tab 为: { current_tab . value } " )В ленивом режиме загрузки будет отображаться только вкладка в настоящее время.
from ex4nicegui import to_ref , rxui , on , deep_ref
current_tab = to_ref ( "t1" )
with rxui . tabs ( current_tab ):
ui . tab ( "t1" )
ui . tab ( "t2" )
with rxui . lazy_tab_panels ( current_tab ) as panels :
@ panels . add_tab_panel ( "t1" )
def _ ():
# 通过 `panels.get_panel` 获取当前激活的 panel 组件
panels . get_panel ( "t1" ). classes ( "bg-green" )
ui . notify ( "Hello from t1" )
ui . label ( "This is t1" )
@ panels . add_tab_panel ( "t2" )
def _ ():
panels . get_panel ( "t2" ). style ( "background-color : red" )
ui . notify ( "Hello from t2" )
ui . label ( "This is t2" )После загрузки страницы «Привет из T1» отображается немедленно. При переходе на вкладку «T2» будет отображаться «Hello From T2».
Метод scoped_style позволяет создавать стили, ограниченные компонентом.
# 所有子元素都会有红色轮廓,但排除自身
with rxui . row (). scoped_style ( "*" , "outline: 1px solid red;" ) as row :
ui . label ( "Hello" )
ui . label ( "World" )
# 所有子元素都会有红色轮廓,包括自身
with rxui . row (). scoped_style ( ":self *" , "outline: 1px solid red;" ) as row :
ui . label ( "Hello" )
ui . label ( "World" )
# 当鼠标悬停在 row 组件时,所有子元素都会有红色轮廓,但排除自身
with rxui . row (). scoped_style ( ":hover *" , "outline: 1px solid red;" ) as row :
ui . label ( "Hello" )
ui . label ( "World" )
# 当鼠标悬停在 row 组件时,所有子元素都会有红色轮廓,包括自身
with rxui . row (). scoped_style ( ":self:hover *" , "outline: 1px solid red;" ) as row :
ui . label ( "Hello" )
ui . label ( "World" )Создать интерактивные данные о визуальных отчетах с наиболее оптимизированными API

from nicegui import ui
import pandas as pd
import numpy as np
from ex4nicegui import bi
from ex4nicegui . reactive import rxui
from ex4nicegui import effect , effect_refreshable
from pyecharts . charts import Bar
# data ready
def gen_data ():
np . random . seed ( 265 )
field1 = [ "a1" , "a2" , "a3" , "a4" ]
field2 = [ f"name { i } " for i in range ( 1 , 11 )]
df = (
pd . MultiIndex . from_product ([ field1 , field2 ], names = [ "cat" , "name" ])
. to_frame ()
. reset_index ( drop = True )
)
df [[ "idc1" , "idc2" ]] = np . random . randint ( 50 , 1000 , size = ( len ( df ), 2 ))
return df
df = gen_data ()
# 创建数据源
ds = bi . data_source ( df )
# ui
ui . query ( ".nicegui-content" ). classes ( "items-stretch no-wrap" )
with ui . row (). classes ( "justify-evenly" ):
# 基于数据源 `ds` 创建界面组件
ds . ui_select ( "cat" ). classes ( "min-w-[10rem]" )
ds . ui_select ( "name" ). classes ( "min-w-[10rem]" )
with ui . grid ( columns = 2 ):
# 使用字典配置图表
@ ds . ui_echarts
def bar1 ( data : pd . DataFrame ):
data = data . groupby ( "name" ). agg ({ "idc1" : "sum" , "idc2" : "sum" }). reset_index ()
return {
"xAxis" : { "type" : "value" },
"yAxis" : {
"type" : "category" ,
"data" : data [ "name" ]. tolist (),
"inverse" : True ,
},
"legend" : { "textStyle" : { "color" : "gray" }},
"series" : [
{ "type" : "bar" , "name" : "idc1" , "data" : data [ "idc1" ]. tolist ()},
{ "type" : "bar" , "name" : "idc2" , "data" : data [ "idc2" ]. tolist ()},
],
}
bar1 . classes ( "h-[20rem]" )
# 使用pyecharts配置图表
@ ds . ui_echarts
def bar2 ( data : pd . DataFrame ):
data = data . groupby ( "name" ). agg ({ "idc1" : "sum" , "idc2" : "sum" }). reset_index ()
return (
Bar ()
. add_xaxis ( data [ "name" ]. tolist ())
. add_yaxis ( "idc1" , data [ "idc1" ]. tolist ())
. add_yaxis ( "idc2" , data [ "idc2" ]. tolist ())
)
bar2 . classes ( "h-[20rem]" )
# 绑定点击事件,即可实现跳转
@ bar2 . on_chart_click
def _ ( e : rxui . echarts . EChartsMouseEventArguments ):
ui . open ( f"/details/ { e . name } " , new_tab = True )
# 利用响应式机制,你可以随意组合原生 nicegui 组件
label_a1_total = ui . label ( "" )
# 当 ds 有变化,都会触发此函数
@ effect
def _ ():
# filtered_data 为过滤后的 DataFrame
df = ds . filtered_data
total = df [ df [ "cat" ] == "a1" ][ "idc1" ]. sum ()
label_a1_total . text = f"idc1 total(cat==a1): { total } "
# 你也可以使用 `effect_refreshable`,但需要注意函数中的组件每次都被重建
@ effect_refreshable
def _ ():
df = ds . filtered_data
total = df [ df [ "cat" ] == "a2" ][ "idc1" ]. sum ()
ui . label ( f"idc1 total(cat==a2): { total } " )
# 当点击图表系列时,跳转的页面
@ ui . page ( "/details/{name}" )
def details_page ( name : str ):
ui . label ( "This table data will not change" )
ui . aggrid . from_pandas ( ds . data . query ( f'name==" { name } "' ))
ui . label ( "This table will change when the homepage data changes. " )
@ bi . data_source
def new_ds ():
return ds . filtered_data [[ "name" , "idc1" , "idc2" ]]
new_ds . ui_aggrid ()
ui . run ()bi.data_sourceИсточник данных является основной концепцией модуля BI, и на этом основана связь всех данных. В текущей версии (0.4.3) есть два способа создания источников данных.
DataFrame , который получает pandas :
from nicegui import ui
from ex4nicegui import bi
import pandas as pd
df = pd . DataFrame (
{
"name" : list ( "aabcdf" ),
"cls" : [ "c1" , "c2" , "c1" , "c1" , "c3" , None ],
"value" : range ( 6 ),
}
)
ds = bi . data_source ( df )Иногда мы хотим создать новый источник данных на основе другого источника данных, и в настоящее время мы можем использовать декоратор для создания источника данных связи:
df = pd . DataFrame (
{
"name" : list ( "aabcdf" ),
"cls" : [ "c1" , "c2" , "c1" , "c1" , "c3" , None ],
"value" : range ( 6 ),
}
)
ds = bi . data_source ( df )
@ bi . data_source
def new_ds ():
# df is pd.DataFrame
df = ds . filtered_data
df = df . copy ()
df [ 'value' ] = df [ 'value' ] * 100
return df
ds . ui_select ( 'name' )
new_ds . ui_aggrid () Обратите внимание, что, поскольку ds.filtered_data используется в new_ds , изменение ds запустит изменение сцепления new_ds , что приведет к изменениям в компонентах таблицы, созданных new_ds
Удалите все статус фильтра с помощью метода ds.remove_filters :
ds = bi . data_source ( df )
def on_remove_filters ():
ds . remove_filters ()
ui . button ( "remove all filters" , on_click = on_remove_filters )
ds . ui_select ( "name" )
ds . ui_aggrid () Сбросить источник данных с помощью метода ds.reload :
df = pd . DataFrame (
{
"name" : list ( "aabcdf" ),
"cls" : [ "c1" , "c2" , "c1" , "c1" , "c3" , None ],
"value" : range ( 6 ),
}
)
new_df = pd . DataFrame (
{
"name" : list ( "xxyyds" ),
"cls" : [ "cla1" , "cla2" , "cla3" , "cla3" , "cla3" , None ],
"value" : range ( 100 , 106 ),
}
)
ds = bi . data_source ( df )
def on_remove_filters ():
ds . reload ( new_df )
ui . button ( "reload data" , on_click = on_remove_filters )
ds . ui_select ( "name" )
ds . ui_aggrid () from nicegui import ui
from ex4nicegui import bi
import pandas as pd
df = pd . DataFrame (
{
"name" : list ( "aabcdf" ),
"cls" : [ "c1" , "c2" , "c1" , "c1" , "c3" , None ],
"value" : range ( 6 ),
}
)
ds = bi . data_source ( df )
ds . ui_select ( "name" )В столбце первого параметра указывается имя столбца источника данных
Установите порядок параметров по параметрам sort_options :
ds . ui_select ( "name" , sort_options = { "value" : "desc" , "name" : "asc" }) Параметр exclude_null_value устанавливает, исключать ли нулевые значения:
df = pd . DataFrame (
{
"cls" : [ "c1" , "c2" , "c1" , "c1" , "c3" , None ],
}
)
ds = bi . data_source ( df )
ds . ui_select ( "cls" , exclude_null_value = True )Вы можете установить параметры нативного компонента NiceGui с помощью параметров ключевых слов.
Установите значение по умолчанию через атрибут значения:
ds . ui_select ( "cls" , value = [ 'c1' , 'c2' ])
ds . ui_select ( "cls" , multiple = False , value = 'c1' ) Когда несколько выборов (параметр multiple по умолчанию верно), value должно быть указано в виде списка
При выборе value установите не list
лист
from nicegui import ui
from ex4nicegui import bi
import pandas as pd
data = pd . DataFrame ({ "name" : [ "f" , "a" , "c" , "b" ], "age" : [ 1 , 2 , 3 , 1 ]})
ds = bi . data_source ( data )
ds . ui_table (
columns = [
{ "label" : "new colA" , "field" : "colA" , "sortable" : True },
]
)ui.table . field ключа соответствует имени столбца источника данных. Если его не существует, конфигурация не вступит в силу. from nicegui import ui
from ex4nicegui import bi
import pandas as pd
data = pd . DataFrame (
{
"colA" : list ( "abcde" ),
"colB" : [ f"n { idx } " for idx in range ( 5 )],
"colC" : list ( range ( 5 )),
}
)
df = pd . DataFrame ( data )
source = bi . data_source ( df )
source . ui_aggrid (
options = {
"columnDefs" : [
{ "headerName" : "xx" , "field" : "no exists" },
{ "headerName" : "new colA" , "field" : "colA" },
{
"field" : "colC" ,
"cellClassRules" : {
"bg-red-300" : "x < 3" ,
"bg-green-300" : "x >= 3" ,
},
},
],
"rowData" : [{ "colX" : [ 1 , 2 , 3 , 4 , 5 ]}],
}
)ui.aggrid . field значения ключа в columnDefs соответствует имени столбца источника данных. Если его не существует, конфигурация не вступит в силу.rowData не вступит в силу. Поскольку источник данных в таблице всегда контролируется источником данных Модуль toolbox предоставляет некоторые обычно используемые функции инструментов.
from ex4nicegui import toolbox Переключить темный режим
from ex4nicegui import rxui , toolbox as tb
from nicegui import ui
dark = tb . use_dark ( False )
rxui . label ( lambda : f"暗模式: { dark . value } " )
rxui . button (
icon = lambda : "sunny" if dark . value else "dark_mode" ,
color = lambda : "red" if dark . value else "blue" ,
on_click = dark . toggle ,
). props ( "flat round" )Отзывчивая точка останова
from ex4nicegui import rxui , toolbox as tb
from nicegui import ui
options = { "手机" : 0 , "平板" : 640 , "笔记本" : 1024 , "桌面" : 1280 }
bp = tb . use_breakpoints ( options )
active = bp . active
is_between = bp . between ( "手机" , "笔记本" )
with ui . card ():
rxui . label ( lambda : f"当前断点: { active . value } " )
rxui . label ( lambda : f"是否在手机-笔记本(不含)之间: { is_between . value } " ). bind_classes (
{ "text-red-500" : is_between }
)
rxui . label ( lambda : f'手机(0px - 640px): { active . value == "手机" } ' ). bind_classes (
{ "bg-red-300" : lambda : active . value == "手机" }
)
rxui . label ( lambda : f'平板(640px - 1024px): { active . value == "平板" } ' ). bind_classes (
{ "bg-red-300" : lambda : active . value == "平板" }
)
rxui . label (
lambda : f'笔记本(1024px - 1280px): { active . value == "笔记本" } '
). bind_classes ({ "bg-red-300" : lambda : active . value == "笔记本" })
rxui . label ( lambda : f'桌面(1280px+): { active . value == "桌面" } ' ). bind_classes (
{ "bg-red-300" : lambda : active . value == "桌面" }
)
Генерировать QR -код
from ex4nicegui import rxui , to_ref , toolbox as tb
from nicegui import ui
text = to_ref ( "ex4nicegui" )
qr_code = tb . use_qr_code ( text )
rxui . input ( value = text )
rxui . image ( qr_code . code ). classes ( "w-20 h-20" ). props ( "no-transition" )