跳到主要内容

导航和路由

单页应用程序(SPA)中的导航和路由是组织应用程序用户界面为虚拟页面(视图)并在应用程序 URL 反映当前状态的基本功能。

对于移动应用程序,导航和路由充当深度链接到应用程序特定部分的角色。

添加导航和路由到 Flet 中需要付出更多努力,因为实现基于 Flutter 的 Navigator 2.0 API,並且需要将 Flet 的“Page”抽象替换为“Page 和 View”。 Flutter 的新导航和路由 API 带来了实质性的改进,如:

  1. 对历史栈的程序化控制。
  2. 轻松拦截 AppBar 中的“Back”按钮调用。
  3. 与浏览器历史记录的robust同步。

探索上面的示例代码

页面路由

页面路由是应用程序 URL 中的 # 符号后的部分:

默认应用程序路由,如果用户未在应用程序 URL 中设置,则为 /。所有路由都以 / 开头,例如 /store/authors/1/books/2

可以通过读取 page.route 属性来获取应用程序路由,例如:

import flet as ft

def main(page: ft.Page):
page.add(ft.Text(f"初始路由:{page.route}"))

ft.app(target=main, view=ft.AppView.WEB_BROWSER)

获取应用程序 URL,在新浏览器标签页中打开,修改 URL 中的 # 符号后的部分为 /test,然后按 Enter 键。你应该看到“初始路由:/test”。

每当 URL 中的路由更改(通过编辑 URL 或使用 Back/Forward 按钮浏览历史记录)时,Flet 都会调用 page.on_route_change 事件处理程序:

import flet as ft

def main(page: ft.Page):
page.add(ft.Text(f"初始路由:{page.route}"))

def route_change(e: ft.RouteChangeEvent):
page.add(ft.Text(f"新路由:{e.route}"))

page.on_route_change = route_change
page.update()

ft.app(target=main, view=ft.AppView.WEB_BROWSER)

现在尝试更新 URL 几次,然后使用 Back/Forward 按钮!你应该看到每次路由更改时都添加了一个新消息:

路由可以通过更新 page.route 属性来更改:

import flet as ft

def main(page: ft.Page):
page.add(ft.Text(f"初始路由:{page.route}"))

def route_change(e: ft.RouteChangeEvent):
page.add(ft.Text(f"新路由:{e.route}"))

def go_store(e):
page.route = "/store"
page.update()

page.on_route_change = route_change
page.add(ft.ElevatedButton("Go to Store", on_click=go_store))

ft.app(target=main, view=ft.AppView.WEB_BROWSER)

点击“Go to Store”按钮,你将看到应用程序 URL 更改,并在浏览器历史记录中添加了一个新项。你可以使用浏览器的“Back”按钮来导航回之前的路由。

页面视图

Flet 的 Page 现在不仅是一个单页,还是一个容器,用于容纳 View 层叠在一起:

视图集合表示导航历史记录。Page 有一个 page.views 属性来访问视图集合。

视图列表中的最后一个视图是当前显示在页面上的视图。视图列表至少需要有一个元素(根视图)。

要模拟页面之间的过渡,需要更改 page.route 并在 page.view 列表末尾添加一个新视图。

page.on_view_pop 事件处理程序中,从视图列表中弹出最后一个视图,并更改路由以返回到“前一个”路由,以便返回。

根据路由更改构建视图

要构建可靠的导航,程序中必须有一个地方根据当前路由构建视图列表。换言之,导航历史记录栈(由视图列表表示)必须是路由的函数。

这个地方是 page.on_route_change 事件处理程序。

让我们将所有内容组合成一个完整的示例,该示例允许在两个页面之间导航:

import flet as ft

def main(page: ft.Page):
page.title = "Routes Example"

def route_change(route):
page.views.clear()
page.views.append(
ft.View(
"/",
[
ft.AppBar(title=ft.Text("Flet app"), bgcolor=ft.Colors.SURFACE_VARIANT),
ft.ElevatedButton("Visit Store", on_click=lambda _: page.go("/store")),
],
)
)
if page.route == "/store":
page.views.append(
ft.View(
"/store",
[
ft.AppBar(title=ft.Text("Store"), bgcolor=ft.Colors.SURFACE_VARIANT),
ft.ElevatedButton("Go Home", on_click=lambda _: page.go("/")),
],
)
)
page.update()

def view_pop(view):
page.views.pop()
top_view = page.views[-1]
page.go(top_view.route)

page.on_route_change = route_change
page.on_view_pop = view_pop
page.go(page.route)


ft.app(target=main, view=ft.AppView.WEB_BROWSER)

尝试使用“Visit Store”和“Go Home”按钮、Back/Forward 浏览器按钮、手动更改 URL 中的路由 - 它无论如何都能工作! :)

备注

要“导航”到页面,我们使用了 page.go(route) - 一个 helper 方法,它更新 page.route,调用 page.on_route_change 事件处理程序以更新视图,最后调用 page.update()

注意使用 page.on_view_pop 事件处理程序。在处理程序中,我们从视图列表中删除最后一个元素,并导航到视图的根“下面”它。

路由模板

Flet 提供了 TemplateRoute - 一个基于 repath 库的实用程序类,它允许匹配 ExpressJS 风格的路由并解析其参数,例如 /account/:account_id/orders/:order_id

TemplateRoute 与路由更改事件非常配合:

troute = TemplateRoute(page.route)

if troute.match("/books/:id"):
print("Book view ID:", troute.id)
elif troute.match("/account/:account_id/orders/:order_id"):
print("Account:", troute.account_id, "Order:", troute.order_id)
else:
print("Unknown route")

你可以在 repath 库的文档中阅读更多关于模板语法的信息。

Web URL 策略

Flet Web 应用程序支持两种配置 URL 基于路由的方式:

  • Path(默认)- 路径读取和写入不带哈希。例如,fletapp.dev/path/to/view
  • Hash - 路径读取和写入到哈希片段。例如,fletapp.dev/#/path/to/view

要更改 URL 策略,请使用 flet.app() 方法的 route_url_strategy 参数,例如:

ft.app(target=main, route_url_strategy="hash")

Flet Server 的 URL 策略可以使用 FLET_ROUTE_URL_STRATEGY 环境变量配置,该变量可以设置为 path(默认)或 hash