iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 25
1
Modern Web

新時代的網頁框架比較-- 淺談Rails、Django、Phoenix、Laravel系列 第 25

Phoenix起步走:建立一個購物網站--上傳圖片

基本的CRUD完成後,我們要試著挑戰稍微進階一點的功能,例如今天的上傳圖片。

新增欄位

首先我們在產品的model中新增圖片的欄位:

$ mix ecto.gen.migration add_photo_to_product
* creating priv/repo/migrations/20171228003556_add_photo_to_product.exs

編輯migration

  def change do
    alter table(:products) do
      add :photo, :string, default: ""
    end
  end

model的schema要手動新增欄位

  schema "products" do
    field :description, :string
    field :price, :integer
    field :quantity, :integer
    field :title, :string
    field :photo, :string, default: ""

    timestamps()
  end

然後執行mix ecto.migrate

安裝arc_ecto, arc

就像在ruby我們會使用像是carrierwave來處理圖片,在Elixir我們也會使用mix來處理圖片上傳,這邊我們安裝arc_ecto與arc,打開mix.exs

  defp deps do
    [
      ...
      {:arc_ecto, "~> 0.4.4"},
      {:arc, "~> 0.5.3"}
    ]
  end

接著執行mix deps.get來安裝套件。如果安裝時出現錯誤訊息:Hex dependency resolution failed,可以試著更新所有相關的套件mix deps.update --all

建立uploader

有了arc以後,我們就可以建立upoader:

$ mix arc.g photo_uploader
...
* creating web/uploaders/photo_uploader.ex

因為這是Phoenix 2.1.5以前的結構,所以我們手動調整一下:

$ mkdir lib/shop_web/uploaders
$ mv web/uploaders/photo_uploader.ex lib/shop_web/uploaders/photo_uploader.ex
$ rm -r web

然後對檔案做下面的修正:

defmodule ShopWeb.PhotoUploader do
    use Arc.Definition
    use Arc.Ecto.Definition
    
    @versions [:original, :thumb]
    def transform(:thumb, _) do
      {:convert, "-strip -thumbnail 100x100^ -gravity center -extent 100x100 -format png", :png}
    end
    def __storage, do: Arc.Storage.Local
    def filename(version,  {file, scope}), do: "#{version}-#{file.file_name}"
end

記得修改model的部分(貼上有加號的行數,加號記得刪除):

defmodule Shop.Goods.Product do
  use Ecto.Schema
 +use Arc.Ecto.Schema
  import Ecto.Changeset
  alias Shop.Goods.Product

  schema "products" do
    field :description, :string
    field :price, :integer
    field :quantity, :integer
    field :title, :string
   +field :photo, ShopWeb.PhotoUploader.Type

    timestamps()
  end

  @doc false
  def changeset(%Product{} = product, attrs) do
    product
   +|> cast_attachments(attrs, [:photo])
    |> cast(attrs, [:title, :description, :quantity, :price])
    |> validate_required([:title, :description, :quantity, :price])
  end
end

完成後執行mix ecto.migrate

前端上傳圖片

# lib/shop_web/templates/admin/product/form.html.eex
<%= form_for @changeset, @action, [multipart: true], fn f -> %>
  ...
  <div class="form-group">
    <%= label f, :photo, class: "control-label" %>
    <%= file_input f, :photo, class: "form-control" %>
    <%= error_tag f, :photo %>
  </div>
  ...

打開畫面,就可以看到出現了photo檔案選擇的欄位
photo upload

如果你試著上傳圖片,很可能會遇到跟我一樣的錯誤:找不到crypto.rand_bytes方法,這時候請到deps/arc/lib/arc/transformations/convert.ex,手動把他改為crypto.strong_rand_bytes,然後編譯mix deps.compile,這時候再重開伺服器,應該就可以解決了。

顯示圖片

首先在新增下面這段,不要把原本的plug Plug.Static覆蓋掉:

# lib/shop_web/endpoint.ex
plug Plug.Static,
    at: "admin/uploads", from: Path.expand('./uploads'), gzip: false

接著在index.html.eex新增下面這段(或任何你希望出現圖片的地方)

...
<%= product.title %>
<%= if product.photo.file_name != "" do %>
  <img class="thumbnail"
     src="<%= Shop.PhotoUploader.url({product.photo, product}, :thumb) %>"/>
<% end %>
...

這樣圖片就可以顯示了
display photo

參考資料:上傳圖片


上一篇
Phoenix起步走:建立一個購物網站--會員管理
下一篇
在2017年底,我們聊一聊Go (Golang)
系列文
新時代的網頁框架比較-- 淺談Rails、Django、Phoenix、Laravel31

尚未有邦友留言

立即登入留言