查看源代码 上传
LiveView 支持交互式文件上传,并提供上传进度,包括直接上传到服务器和直接上传到云端的 外部上传。
内置功能
接受规范 - 定义可接受的文件类型、最大条目数、最大文件大小等。当客户端选择文件时,文件元数据会自动根据规范进行验证。参见
Phoenix.LiveView.allow_upload/3
。响应式条目 - 上传会在 socket 中的
@uploads
赋值中进行填充。条目会自动响应进度、错误、取消等。拖放 - 使用
phx-drop-target
属性来启用。参见Phoenix.Component.live_file_input/1
。
允许上传
通常在挂载时通过 allow_upload/3
来启用上传。
@impl Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:uploaded_files, [])
|> allow_upload(:avatar, accept: ~w(.jpg .jpeg), max_entries: 2)}
end
暂时就这些!我们稍后将回到 LiveView 实现一些与表单和上传相关的回调,但大多数上传功能在模板中实现。
渲染响应式元素
使用 Phoenix.Component.live_file_input/1
组件来渲染上传的文件输入框。
<%!-- lib/my_app_web/live/upload_live.html.heex --%>
<form id="upload-form" phx-submit="save" phx-change="validate">
<.live_file_input upload={@uploads.avatar} />
<button type="submit">Upload</button>
</form>
重要: 您必须在表单上绑定
phx-submit
和phx-change
。
请注意,虽然 live_file_input/1
允许您在文件输入框上设置额外的属性,但许多属性(例如 id
、accept
和 multiple
)会根据 allow_upload/3
规范自动设置。
当最终用户与文件输入框交互时,模板将进行响应式更新。
上传条目
上传会在 socket 中的 @uploads
赋值中进行填充。每个允许的上传都包含一个条目列表,而不管 allow_upload/3
规范中的 :max_entries
值如何。这些条目结构包含有关上传的所有信息,包括进度、客户端文件信息、错误等。
让我们看一个带注释的示例。
<%!-- lib/my_app_web/live/upload_live.html.heex --%>
<%!-- use phx-drop-target with the upload ref to enable file drag and drop --%>
<section phx-drop-target={@uploads.avatar.ref}>
<%!-- render each avatar entry --%>
<%= for entry <- @uploads.avatar.entries do %>
<article class="upload-entry">
<figure>
<.live_img_preview entry={entry} />
<figcaption><%= entry.client_name %></figcaption>
</figure>
<%!-- entry.progress will update automatically for in-flight entries --%>
<progress value={entry.progress} max="100"> <%= entry.progress %>% </progress>
<%!-- a regular click event whose handler will invoke Phoenix.LiveView.cancel_upload/3 --%>
<button type="button" phx-click="cancel-upload" phx-value-ref={entry.ref} aria-label="cancel">×</button>
<%!-- Phoenix.Component.upload_errors/2 returns a list of error atoms --%>
<%= for err <- upload_errors(@uploads.avatar, entry) do %>
<p class="alert alert-danger"><%= error_to_string(err) %></p>
<% end %>
</article>
<% end %>
<%!-- Phoenix.Component.upload_errors/1 returns a list of error atoms --%>
<%= for err <- upload_errors(@uploads.avatar) do %>
<p class="alert alert-danger"><%= error_to_string(err) %></p>
<% end %>
</section>
示例中的 section
元素充当 :avatar
上传的 phx-drop-target
。用户可以与文件输入框交互,也可以将文件拖放到元素上来添加新条目。
当文件添加到表单输入框中时,会创建上传条目,每个条目都将在成功完成上传后被消耗掉之前一直存在。
条目验证
验证会根据 allow_upload/3
中指定的任何条件自动进行,但如前所述,您需要在表单上绑定 phx-change
才能执行验证。因此,您必须至少实现一个最小的回调。
@impl Phoenix.LiveView
def handle_event("validate", _params, socket) do
{:noreply, socket}
end
不符合 allow_upload/3
规范的文件的条目将包含错误。使用 Phoenix.Component.upload_errors/2
和您自己的帮助函数来渲染友好的错误消息。
defp error_to_string(:too_large), do: "Too large"
defp error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
对于影响所有条目的错误消息,请使用 Phoenix.Component.upload_errors/1
和您自己的帮助函数来渲染友好的错误消息。
defp error_to_string(:too_many_files), do: "You have selected too many files"
取消条目
上传条目也可以取消,无论是以编程方式还是由用户操作引起。例如,要处理上面模板中的点击事件,您可以执行以下操作。
@impl Phoenix.LiveView
def handle_event("cancel-upload", %{"ref" => ref}, socket) do
{:noreply, cancel_upload(socket, :avatar, ref)}
end
消耗已上传的条目
当最终用户提交包含 live_file_input/1
的表单时,JavaScript 客户端会先上传文件,然后再调用表单 phx-submit
事件的回调。
在 phx-submit
事件的回调中,您可以调用 Phoenix.LiveView.consume_uploaded_entries/3
函数来处理已完成的上传,并将相关的上传数据与表单数据一起持久化。
@impl Phoenix.LiveView
def handle_event("save", _params, socket) do
uploaded_files =
consume_uploaded_entries(socket, :avatar, fn %{path: path}, _entry ->
dest = Path.join(Application.app_dir(:my_app, "priv/static/uploads"), Path.basename(path))
# You will need to create `priv/static/uploads` for `File.cp!/2` to work.
File.cp!(path, dest)
{:ok, ~p"/uploads/#{Path.basename(dest)}"}
end)
{:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))}
end
注意:虽然不能信任客户端元数据,但在执行直接上传到服务器时,会对每个接收到的块进行最大文件大小验证。
此示例将文件直接写入磁盘,位于 priv
文件夹下。为了访问您的上传,例如在 <img />
标签中,您需要将 uploads
目录添加到 static_paths/0
。在普通的 Phoenix 项目中,这在 lib/my_app_web.ex
中。
另一件需要注意的是,在开发环境中,对 priv/static/uploads
的更改会被 live_reload
捕获。这意味着,一旦您的上传成功,您的应用程序就会在浏览器中重新加载。这可以通过在 config/dev.exs
中设置 code_reloader: false
来暂时禁用。
除了上述内容外,此方法在生产环境中也有局限性。如果您运行了多个应用程序实例,上传的文件将仅存储在一个实例中。路由到其他机器的任何请求最终都会失败。
出于这些原因,最好将上传存储在其他地方,例如数据库(取决于大小和内容)或单独的存储服务。有关实现客户端直接上传到云的更多信息,请参见 外部上传指南 以了解详细信息。
附录 A: UploadLive
本指南中 LiveView 的完整示例。
# lib/my_app_web/live/upload_live.ex
defmodule MyAppWeb.UploadLive do
use MyAppWeb, :live_view
@impl Phoenix.LiveView
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:uploaded_files, [])
|> allow_upload(:avatar, accept: ~w(.jpg .jpeg), max_entries: 2)}
end
@impl Phoenix.LiveView
def handle_event("validate", _params, socket) do
{:noreply, socket}
end
@impl Phoenix.LiveView
def handle_event("cancel-upload", %{"ref" => ref}, socket) do
{:noreply, cancel_upload(socket, :avatar, ref)}
end
@impl Phoenix.LiveView
def handle_event("save", _params, socket) do
uploaded_files =
consume_uploaded_entries(socket, :avatar, fn %{path: path}, _entry ->
dest = Path.join([:code.priv_dir(:my_app), "static", "uploads", Path.basename(path)])
# You will need to create `priv/static/uploads` for `File.cp!/2` to work.
File.cp!(path, dest)
{:ok, ~p"/uploads/#{Path.basename(dest)}"}
end)
{:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))}
end
defp error_to_string(:too_large), do: "Too large"
defp error_to_string(:too_many_files), do: "You have selected too many files"
defp error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
end
要通过应用程序访问您的上传,请确保将 uploads
添加到 MyAppWeb.static_paths/0
。