2 - 後台管理

一切就緒後,首先從後台開始做 story: 「管理者要有一個後台」 => 有 localhost:4000/admin/products 這個網址 => 修改 router.ex

# web/router.ex
...
  scope "/admin", ShoppingSite.Admin, as: :admin do
    pipe_through :browser

    resources "/products", ProductController
  end
...

第一行 ShoppingSite.Admin
讓下面的 resource都不用再寫前綴
後面的 as: :admin
讓 path前面都加上 admin_ (如下圖)
避免同一個 resource的同名混淆

mix phoenix.routes 看看現在網址是我們要的嗎?

   page_path  GET     /                         ShoppingSite.PageController :index
admin_product_path  GET     /admin/products           ShoppingSite.Admin.ProductController :index
admin_product_path  GET     /admin/products/:id/edit  ShoppingSite.Admin.ProductController :edit
admin_product_path  GET     /admin/products/new       ShoppingSite.Admin.ProductController :new
admin_product_path  GET     /admin/products/:id       ShoppingSite.Admin.ProductController :show
admin_product_path  POST    /admin/products           ShoppingSite.Admin.ProductController :create
admin_product_path  PATCH   /admin/products/:id       ShoppingSite.Admin.ProductController :update
                    PUT     /admin/products/:id       ShoppingSite.Admin.ProductController :update
admin_product_path  DELETE  /admin/products/:id       ShoppingSite.Admin.ProductController :delete

great~~

story:「產品要有標題、文字、數量、敘述」=> 產生 product model command: mix phoenix.gen.model product products title:string description:text quantity:integer price:integer

修改 product model檔案, 讓 changeset會去檢查是否四項都是有輸入, 然後弄成以後可以擴充圖片

# web/models/product.ex
...
  @required_fields ~w(title description quantity price)
  @optional_fields ~w()

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(model, params \\ %{}) do
    model
    |> cast(params, @required_fields, @optional_fields)
    |> validate_required([:title, :description, :quantity, :price])    #must be atom
    |> validate_length(:description, max: 200)
  end

migration 指定資料輸入時不可為空

# priv/repo/migration/XXXXX_create_product.exs
...
  def change do
    create table(:products) do
      add :title, :string, null: false
      add :description, :text, null: false
      add :quantity, :integer, null: false
      add :price, :integer, null: false

      timestamps()
    end

  end
...

then mix ecto.migrate !

我們可以檢查一下我們寫的 changeset 到底對不對

iex> alias ShoppingSite.Product
iex> params = %{title: "product_1", description: "test", price: 100, quantity: 1}
iex> changeset = Product.changeset(%Product{}, params)
     #Ecto.Changeset ....
iex> changeset.valid?
true

有顯示 true 表示 changeset 有成功


new、create in controller
新增 web/controllers/admin資料夾
新增 web/controllers/admin/product_controller.ex

# web/controllers/admin/product_controller.ex
defmodule ShoppingSite.Admin.ProductController do
  use ShoppingSite.Web, :controller

  alias ShoppingSite.Product

  def new(conn, params) do
    changeset = Product.changeset(%Product{})
    render conn, "new.html", changeset: changeset
  end

  def create(conn, %{"product" => product_params}) do
    changeset = Product.changeset(%Product{}, product_params)

    case Repo.insert(changeset) do
      {:ok, _product} ->
        conn
        |> put_flash(:info, "Add product successfully")
        |> redirect(to: page_path(conn, :index))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end
end

增加 product_view.ex, 因為在 router檔案我們是寫 ShoppingSite.Admin 所以 view, template都要新增 admin 資料夾,phoenix才讀得到

# web/views/admin/product_view.ex
defmodule ShoppingSite.Admin.ProductView do
  use ShoppingSite.Web,   :view

end

把輸入產品規格的表格做成可以重複使用的 form.html

#web/templates/admin/form.html.eex
<%= form_for @changeset, @action , fn f -> %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>Oops! something went wrong!</p>
    </div>
  <%= end %>

  <div class="form-group">
    <%= label f, :title, class: "control-label" %>
    <%= text_input f, :title, class: "form-control" %>
    <%= error_tag f, :title %>
  </div>

  <div class="form-group">
    <%= label f, :description, class: "control-label" %>
    <%= text_input f, :description, class: "form-control" %>
    <%= error_tag f, :description %>
  </div>

  <div class="form-group">
    <%= label f, :price, class: "control-label" %>
    <%= text_input f, :price, class: "form-control" %>
    <%= error_tag f, :price %>
  </div>

  <div class="form-group">
    <%= label f, :quantity, class: "control-label" %>
    <%= text_input f, :quantity, class: "form-control" %>
    <%= error_tag f, :quantity %>
  </div>

  <div class="form-group">
    <%= submit "Submit", class: "btn btn-primary" %>
  </div>

<% end %>

新增 new.html.eex

#web/templates/admin/new.html.eex
<h1>Create new product</h1>

<%= render "form.html", changeset: @changeset, action: admin_product_path(@conn, :create) %>

<%= link "Back" , to: page_path(@conn, :index) %>

所有 product 清單

#web/controllers/product_controller.ex
def index(conn, _params) do
  products = Repo.all(Product)
  render conn, "index.html", products: products
end

新增 index.html 注意 for 是要用 <%= %> 括起來....

# web/templates/admin/product/index.html.eex
<ul>
  <%= for product <- @products do %>
    <li><%= link product.title , to: admin_product_path(@conn, :show, product.id) %></li>
  <% end %>
</ul>

到這裡就可以上架商品囉!

在網站試試看,submit完在 console輸入ShoppingSite.Repo.all ShoppingSite.Product 要可以看到剛剛上傳的資料

results matching ""

    No results matching ""