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

Phoenix 1.6 LiveView嵌套关联表单新增子项功能实现咨询

Phoenix 1.6 + LiveView 嵌套关联表单新增子项最优实现方案

以下实现完全适配Phoenix 1.6版本特性,无需引入额外依赖。


1. 调整Ecto层配置

首先确保你的Schema和Changeset配置符合关联操作要求:

Parent Schema 示例

schema "parents" do
  field :name, :string
  has_many :children, Child, on_replace: :delete
  timestamps()
end

@doc false
def changeset(parent, attrs) do
  parent
  |> cast(attrs, [:name])
  |> validate_required([:name])
  # 核心配置:允许子项参数入库,此处可根据需求调整是否要求子项必填
  |> cast_assoc(:children, required: false)
end

Child Schema 示例

schema "children" do
  field :name, :string
  belongs_to :parent, Parent
  timestamps()
end

@doc false
def changeset(child, attrs) do
  child
  |> cast(attrs, [:name])
  |> validate_required([:name])
end

注意:has_many配置中的on_replace: :delete是为了支持后续子项删除操作,不需要删除功能可省略


2. LiveView 逻辑实现

初始化表单状态

在mount或更新组件的回调中初始化表单changeset:

# 新建场景
@impl true
def mount(_params, _session, socket) do
  changeset = Parent.changeset(%Parent{}, %{})
  {:ok, assign(socket, :form, to_form(changeset))}
end

# 编辑场景,加载已有关联子项
@impl true
def mount(%{"id" => parent_id}, _session, socket) do
  parent = Repo.get(Parent, parent_id) |> Repo.preload(:children)
  changeset = Parent.changeset(parent, %{})
  {:ok, assign(socket, :form, to_form(changeset))}
end

新增子项事件处理

添加新增子项的事件回调,直接更新changeset中的children关联:

@impl true
def handle_event("add_child", _, socket) do
  current_changeset = socket.assigns.form.source
  existing_children = Ecto.Changeset.get_field(current_changeset, :children, [])
  # 追加新的空Child结构体
  updated_children = existing_children ++ [Child.changeset(%Child{}, %{})]
  
  updated_changeset = 
    current_changeset
    |> Ecto.Changeset.put_assoc(:children, updated_children)
  
  {:noreply, assign(socket, :form, to_form(updated_changeset))}
end

提交表单逻辑

直接使用Parent的changeset处理提交参数即可,Ecto会自动处理关联子项的保存:

@impl true
def handle_event("save", %{"parent" => parent_params}, socket) do
  # 编辑场景替换为已加载的parent实例即可
  data = socket.assigns.form.source.data
  case Parent.changeset(data, parent_params) |> Repo.insert_or_update() do
    {:ok, parent} -> 
      # 保存成功后的逻辑,比如跳转、提示
      {:noreply, socket |> put_flash(:info, "保存成功") |> push_navigate(to: ~p"/parents/#{parent}")}
    {:error, %Ecto.Changeset{} = changeset} -> 
      {:noreply, assign(socket, :form, to_form(changeset))}
  end
end

3. HEEx 模板实现

<.form for={@form} phx-submit="save">
  <%!-- Parent 基础字段 --%>
  <.input field={@form[:name]} label="Parent名称" />

  <%!-- 嵌套子项表单区域 --%>
  <div class="children-section">
    <h3>子项列表</h3>
    <%= for child_form <- inputs_for(@form, :children) do %>
      <div class="child-item">
        <.input field={child_form[:name]} label="Child名称" />
      </div>
    <% end %>

    <%!-- 新增子项按钮,注意type为button避免触发表单提交 --%>
    <button type="button" phx-click="add_child" class="add-btn">
      新增子项
    </button>
  </div>

  <button type="submit" class="submit-btn">保存全部</button>
</.form>

如果是在LiveComponent中实现,需要给按钮和表单加上phx-target={@myself}指定事件接收目标


常见问题排查

  • 如果提交后子项没有保存,优先检查Parent changeset中是否正确配置了cast_assoc(:children)
  • 如果点击新增按钮没有反应,检查按钮的type是否为button,事件名是否与handle_event中定义的一致
  • 如果子项字段修改后页面没有同步,确认你使用的是Phoenix 1.6自带的to_form函数转换changeset,不要使用旧版的form_for语法

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 13:36:05