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
相关产品推荐
相关产品推荐

