You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Phoenix框架中如何为app.html.heex/root.html.heex创建控制器实现全局菜单?

在Phoenix中实现全局缓存菜单(支持Live/非Live页面)

针对你的需求,核心是把菜单数据的查询逻辑从模板移到全局层面,并通过缓存避免重复查询,同时兼容普通页面和LiveView页面。下面是具体实现步骤:

1. 引入缓存依赖(避免重复查库)

先用Cachex来缓存菜单数据,这样数据库查询只会在缓存失效时执行一次。在mix.exs的依赖中添加:

defp deps do
  [
    # 其他依赖...
    {:cachex, "~> 3.6"}
  ]
end

执行mix deps.get安装依赖,然后在lib/my_app/application.ex中启动Cachex服务:

def start(_type, _args) do
  children = [
    # 其他子进程...
    {Cachex, name: :menu_cache}
  ]

  Supervisor.start_link(children, strategy: :one_for_one)
end

2. 为非Live页面注入菜单数据(全局Plug)

创建一个Plug来全局处理菜单数据注入,所有普通HTTP请求都会经过这个Plug:

# lib/my_app_web/plugs/put_menu_items.ex
defmodule MyAppWeb.Plugs.PutMenuItems do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    items =
      case Cachex.get(:menu_cache, :items) do
        {:ok, nil} ->
          # 缓存为空时查询数据库,并存入缓存(设置1小时过期)
          items = MyApp.Items.Item.list_items()
          Cachex.put(:menu_cache, :items, items, ttl: :timer.hours(1))
          items
        {:ok, items} ->
          # 直接返回缓存数据
          items
      end

    assign(conn, :menu_items, items)
  end
end

然后在lib/my_app_web/endpoint.ex的:browser管道中加入这个Plug:

pipeline :browser do
  plug :accepts, ["html"]
  plug :fetch_session
  plug :fetch_live_flash
  plug :put_root_layout, html: {MyAppWeb.Layouts, :root}
  plug :protect_from_forgery
  plug :put_secure_browser_headers
  plug MyAppWeb.Plugs.PutMenuItems # 加入这一行
end

3. 为LiveView页面注入菜单数据(全局钩子)

LiveView有独立的连接生命周期,需要用on_mount钩子全局注入数据:

# 在lib/my_app_web.ex中添加全局LiveView钩子
defmodule MyAppWeb do
  # 其他代码...

  def live_view do
    quote do
      use Phoenix.LiveView,
        layout: {MyAppWeb.Layouts, :live}

      unquote(view_helpers())
      # 注册全局on_mount钩子
      on_mount MyAppWeb.LiveHelpers.PutMenuItems
    end
  end

  # 实现钩子模块
  defmodule LiveHelpers.PutMenuItems do
    import Phoenix.LiveView

    def on_mount(:default, _params, _session, socket) do
      items =
        case Cachex.get(:menu_cache, :items) do
          {:ok, nil} ->
            items = MyApp.Items.Item.list_items()
            Cachex.put(:menu_cache, :items, items, ttl: :timer.hours(1))
            items
          {:ok, items} ->
            items
        end

      {:cont, assign(socket, :menu_items, items)}
    end
  end
end

4. 在布局模板中使用菜单数据

修改app.html.heex或root.html.heex,直接使用注入的@menu_items:

<div>
  <ul>
    <%= for item <- @menu_items do %>
      <li><%= item.name %></li>
    <% end %>
  </ul>
  <!-- 其他页面内容 -->
</div>

关键说明

  • 缓存周期可以根据需求调整(比如ttl: :timer.minutes(30)),如果需要手动刷新缓存,可以调用Cachex.delete(:menu_cache, :items)
  • 不管是普通控制器页面还是LiveView页面,都会自动获取到缓存后的菜单数据,数据库查询只会在缓存缺失时执行一次

内容的提问来源于stack exchange,提问作者Jun Dalisay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 13:27:52