文章目录
  1. 1. 目标
  2. 2. 目标
  3. 3. 步骤
    1. 3.1. Step 0:建立新分支
    2. 3.2. Step 2:
    3. 3.3. Step 3:简单重构
    4. 3.4. Step 4:
    5. 3.5. Step 5:
    6. 3.6. Step 6:
    7. 3.7. Step 7:
    8. 3.8. Step 8:建立app/views/common/_categories.html.erb
    9. 3.9. Step 9:建立app/views/common/_products.html.erb
    10. 3.10. Step 10:
    11. 3.11. Step 11:建立路由
    12. 3.12. Step 12: 美化商品展示页面
    13. 3.13. Step 13:将变更 commit 进去 git 里面。
  4. 4. 本章详解
  5. 5. 步骤
    1. 5.1. Step 1:
    2. 5.2. Step 2:
    3. 5.3. Step 3:(这个好像没有这样改)
    4. 5.4. Step 4:
    5. 5.5. Step 5:
    6. 5.6. Step 6:
    7. 5.7. Step 8: 美化商品展示页面
    8. 5.8. Step 9:
  6. 6. 解释

本系列第1-11章节为全栈营JDstore教程,本系列是在全栈营JDstore教程基础之上进行魔改。这是我参赛作品的复盘记录,部分代码显得有些乱。我会持续完善本系列,希望你能喜欢。如果您在阅读中发现BUG,可反馈至我的邮箱kerzzi@outlook.com

目标

分类和商品展示页面开发

git checkout -b story14# 「从零开始做购物网站」第20章 分类和商品展示页面开发

大家好,本系列文章主要是对我参加全栈营JDstore魔改大赛作品「季风 & MONSOON」的复盘。由于本人也是新手,文章中一定有很多幼稚的错误或代码,希望大家帮忙指出,可反馈至我的邮箱kerzzi@outlook.com,我会持续完善本系列。非常感谢。
本系列是在全栈营JDstore教程基础之上进行魔改,本系列第1-11章节为全栈营JDstore教程,请在完成全栈营JDstore教程的基础上进行测试。

目标

分类和商品展示页面开发

步骤

Step 0:建立新分支

在终端执行

checkout -b story14```。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
### Step 1:
由于分类内容,在很多页面都要用到。所以先打开category.rb 这个model文件,在其中定义一个方法,保证可以在多个页面中,取得这个一二级分类:
```ruby app/model/category.rb
class Category < ApplicationRecord
validates :title, presence: { message: "名称不能为空" }
validates :title, uniqueness: { message: "名称不能重复" }
has_ancestry orphan_strategy: :destroy #删除一级分类时,二级分类自动删除
has_many :products, dependent: :destroy
before_validation :correct_ancestry
+ # 保证可以在多个页面中,取得一二级分类
+ def self.grouped_data
+ self.roots.order("weight desc").inject([]) do |result, parent|
+ row = []
+ row << parent
+ row << parent.children.order("weight desc")
+ result << row
+ end
+ end
private
def correct_ancestry
self.ancestry = nil if self.ancestry.blank?
end
end

Step 2:

在终端执行

g controller categories```。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
```ruby app/controllers/categories_controller.rb
class CategoriesController < ApplicationController
def show
@categories = Category.grouped_data
@category = Category.find(params[:id])
@products = @category.products.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
.order("id desc").includes(:main_product_image)
end
end

show页面需要展示商品分类又展示产品列表,所以需要从数据库中找出并提供这2个数据,@category、 @products。

在终端执行

app/views/categories/show.html.erb```。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这个是二级分类的详情页面,在其中加入如下代码:
```ruby app/views/categories/show.html.erb
<div class="row">
<div class="col-lg-3">
<%= render 'common/categories' %>
</div>
<div class="col-lg-9">
<ol class="breadcrumb">
<li><a href="<%= root_path %>">首页</a></li>
<li class="active"><%= @category.parent.title %></li>
<li class="active"><%= @category.title %></li>
</ol>
<h1><%= @category.title %></h1>
<%= render 'common/products' %>
</div>
</div>

Step 3:简单重构

@categories = Category.grouped_data重复了多次,另外重构后可以便于后续开发。当然重构后使用before_action也是可以的,看个人需求。

打开app/controllers/application_controller.rb,做如下修改

app/controllers/application_controller.rb
1
2
3
4
5
6
7
8
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
+ before_action :fetch_home_data #这样写会增加数据查询量,后续更新
+ def fetch_home_data
+ @categories = Category.grouped_data
+ end

Step 4:

修改app/controllers/categories_controller.rb

app/controllers/categories_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
class CategoriesController < ApplicationController
def show
- @categories = Category.grouped_data
+ fetch_home_data
@category = Category.find(params[:id])
@products = @category.products.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
.order("id desc").includes(:main_product_image)
end
end

Step 5:

同时修改后台app/controllers/admin/base_controller.rb

app/controllers/admin/base_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Admin::BaseController < ActionController::Base
layout 'layouts/admin'
before_action :authenticate_user!
before_action :admin_required
before_action :fetch_home_data #这样写会增加数据查询量,后续更新
def admin_required
if !current_user.admin?
redirect_to "/", alert: "You are not admin."
end
end
def fetch_home_data
@categories = Category.grouped_data
end
end

Step 6:

打开app/controllers/products_controller.rb

app/controllers/products_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ProductsController < ApplicationController
def index
@products = Product.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
.order("id desc")
end
def show
@product = Product.find(params[:id])
end
def add_to_cart
@product = Product.find(params[:id])
if !current_cart.products.include?(@product)
current_cart.add_product_to_cart(@product)
flash[:notice] = "你已成功将 #{@product.title} 加入购物车"
else
flash[:warning] = "你的购物车内已有此物品"
end
redirect_to :back
end
end

这里onshelf用了重构,以便后面多次使用。

打开打开app/model/product.rb,加入onshelf这个scope。,加入onshelf这个scope。

app/model/product.rb
1
2
3
before_create :set_default_attrs #商品建立之前,先生成一个随机序列号
+ scope :onshelf, -> { where(status: Status::On) }

Step 7:

打开app/views/products/index.html.erb,做如下修改

app/views/products/index.html.erb
1
2
3
4
5
6
7
8
<div class="row">
<div class="col-lg-12">
<ol class="breadcrumb">
<li class="active">首页</li>
</ol>
<%= render 'common/products' %>
</div>
</div>

Step 8:建立app/views/common/_categories.html.erb

在终端执行

app/views/common/_categories.html.erb```。
1
2
3
4
5
6
7
8
9
10
11
用来独立的放置商品类别,因为后面会在多个页面使用。
```ruby app/views/common/_categories.html.erb
<ul class="list-group">
<% @categories.each do |group| %>
<li class="list-group-item"><%= group.first.title %></li>
<% group.last.each do |sub_category| %>
<li class="list-group-item"><a href="<%= category_path(sub_category) %>"><%= sub_category.title %></a></li>
<% end -%>
<% end -%>
</ul>

Step 9:建立app/views/common/_products.html.erb

在终端执行

app/views/common/_products.html.erb```。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
```ruby app/views/common/_products.html.erb
<% @products.each do |product| %>
<div class="col-xs-6 col-md-3 text-center product-list-style">
<div>
<% if product.product_images.present? %>
<a href="<%= product_path(product) %>"> <img src=<%= qiniu_image_path(product.main_product_image.image.url) %>></a>
<% else %>
<%= link_to image_tag("http://placehold.it/200x200&text=No Pic", class: "product-list-style-img img-responsive center-block"), product_path(product), target: "_blank" %>
<% end %>
</div>
<br>
<%= link_to(product.title, product_path(product), target: "_blank") %>
<br>
<p><strong>¥ <%= link_to(product.price, product_path(product), target: "_blank") %> 元</strong></p>
</div>
<% end %>

请注意这里我在取出照片时,没有使用product.product_images.first.image.url(:middle),因为这样会再次查询数据库,而且是对所有图片进行检索,这是非常经典的N+1查询问题,会影响网站的性能。为了避免N+1查询,我们可以在products_controller进行如下修改。

打开app/controllers/products_controller.rb,将index action修改成如下内容。

app/controllers/products_controller.rb
1
2
3
4
def index
@products = Product.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
.order("id desc").includes(:product_images)
end

这样在第一次查询数据库时,会同时检索出产品的所有product_images。

Step 10:

但是上一步骤中的设置还是不太好,因为我们在商品迭代输出时并不需要商品的所以图片,仅需要一张即可,为此做如下修改。

在product.rb model中,我们建立产品的一对一关系 main_product_image。

打开app/models/product.rb,增加一对一关系。

app/models/product.rb
1
2
3
4
has_many :product_images, -> { order(weight: 'desc') },
dependent: :destroy
+ has_one :main_product_image, -> { order(weight: 'desc') },
class_name: :ProductImage

这样就可以找到该产品权重最高的一张图片了(即主图片)。这里我使用了

1
2
3
4
5
6
7
8
同时打开prodcuct controller中,进行相应的修改
```ruby app/controllers/products_controller.rb
class ProductsController < ApplicationController
def index
@products = Product.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
.order("id desc").includes(:main_product_image)
end

这样在第一次查询数据库时,会同时检索出产品的main_product_image。

Step 11:建立路由

打开config/routes.rb,增加相应的路由。

config/routes.rb
1
+ resources :categories, only: [:show]

在前端做个简单的样式定义:

app/assets/stylesheets/products.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/****** 商品列表简单样式 ******/
.list-group-item a {
display: block;
}
.msrp {
text-decoration: line-through;
}
.thumbnail {
height: 290px;
&.detail {
height: auto;
}
a.title {
color: #333;
}
}

Step 12: 美化商品展示页面

打开app/views/products/show.html.erb

app/views/products/show.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<div class="container">
<div class="col-lg-3">
<%= render 'common/categories' %>
</div>
<div class="col-lg-9">
<ol class="breadcrumb">
<li><a href="<%= root_path %>">所有宝贝</a></li>
<li class="active"><%= @product.category.parent.title %></li>
<li><a href="<%= category_path @product.category %>"><%= @product.category.title %></a></li>
<li class="active"><%= @product.title %></li>
</ol>
<div class="preview col-md-7 " style="">
<% @product.product_images.each do |product_image| %>
<div class="col-xs-6 col-md-6">
<a href="#" class="thumbnail detail">
<%= image_tag product_image.image.url %>
</a>
</div>
<% end %>
</div>
<div class="preview col-md-5 " style="">
<h1><%= @product.title %></h1>
<ul class="list-unstyled">
<li>商品编号: <%= @product.uuid %></li>
<li>库存: <%= @product.quantity %></li>
</ul>
<h4><span class="msrp">原价: ¥<%= @product.msrp %></span></h4>
<h3><strong>现价: ¥<%= @product.price %></strong> </h3>
<p>购买: <input type="text" name="quantity" value="1" />件</p>
<div class="pull-right">
<% if @product.quantity.present? && @product.quantity > 0 %>
<%= link_to("加入购物车", add_to_cart_product_path(@product), method: :post,
class: "btn btn-lg btn-danger") %>
<% else %>
已销售一空,无法购买
<% end %>
</div>
</div>
<br />
<br />
<ul class="nav nav-tabs">
<li role="presentation" class="active">
<a href="#tab_default_1" data-toggle="tab">宝贝详情</a>
</li>
<li>
<a href="#tab_default_2" data-toggle="tab">宝贝评价(<%= "0" %>) </a>
</li>
</ul>
<br />
<div class="tab-content">
<div class="tab-pane active" id="tab_default_1">
<p class="product-description"><%= @product.description.html_safe %></p>
<p class="product-description2 text-center">产品展示</p>
<div class="row">
<% @product.product_images.each do |product_image| %>
<div class="col-xs-12 col-md-12 product-show-detail">
<%= image_tag product_image.image.url %>
</div>
<% end %>
</div>
<div class="faq">
<p class="faq-title text-center">常见问题</p>
<p class="question">使用什么快递发货?</p>
<p class="answer">伴客默认使用顺丰快递发货,配送范围覆盖全国大部分地区(港澳台地区除外)。</p>
<p class="question">如何申请退货?</p>
<p class="answer">
1.自收到商品之日起7日内,顾客可申请无忧退货,退款将原路返还,不同的银行处理时间不同,预计1-5个工作日到账;<br />
2.退货流程:<br />
确认收货-申请退货-客服审核通过-用户寄回商品-仓库签收验货-退款审核-退款完成;<br />
3.因MONSOON产生的退货,如质量问题,退货邮费由MONSOON承担,退款完成后钱款将沿原渠道返还。因客户个人原因产生的退货,购买和寄回运费由客户个人承担。<br />
</p>
<p class="question">如何开具发票?</p>
<p class="answer">如需开具普通发票,请在下单时联系客服办理,我们将虽货物一起快递给您;</p>
</div>
</div>
</div>
</div>
</div>

Step 13:将变更 commit 进去 git 里面。

1
2
3
4
5
6
git add .
git commit -m "完成分类和商品展示页面初步开发"
git push origin story14
git checkout master
git merge story14
git push origin master

本章详解

努力更新中,上班族请理解。。。。。

步骤

Step 1:

由于分类内容,在很多页面都要用到
所以先打开category.rb 这个model文件,在其中定义一个方法,保证可以在多个页面中,取得这个一二级分类:

app/model/category.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Category < ApplicationRecord
validates :title, presence: { message: "名称不能为空" }
validates :title, uniqueness: { message: "名称不能重复" }
has_ancestry orphan_strategy: :destroy #删除一级分类时,二级分类自动删除
has_many :products, dependent: :destroy
before_validation :correct_ancestry
+ # 保证可以在多个页面中,取得一二级分类
+ def self.grouped_data
+ self.roots.order("weight desc").inject([]) do |result, parent|
+ row = []
+ row << parent
+ row << parent.children.order("weight desc")
+ result << row
+ end
+ end
private
def correct_ancestry
self.ancestry = nil if self.ancestry.blank?
end
end

rails g controller categories

app/controllers/categories_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
class CategoriesController < ApplicationController
def show
@categories = Category.grouped_data
@category = Category.find(params[:id])
@products = @category.products.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
.order("id desc").includes(:main_product_image)
end
end

show页面需要展示商品分类又展示产品列表,所以需要从数据库中找出并提供这2个数据,@category、 @products。

touch app/views/categories/show.html.erb
这个是二级分类的详情页面

这个是二级分类的详情页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="row">
<div class="col-lg-3">
<%= render 'common/categories' %>
</div>
<div class="col-lg-9">
<ol class="breadcrumb">
<li><a href="<%= root_path %>">首页</a></li>
<li class="active"><%= @category.parent.title %></li>
<li class="active"><%= @category.title %></li>
</ol>
<h1><%= @category.title %></h1>
<%= render 'common/products' %>
</div>
</div>

简单重构,@categories = Category.grouped_data重复了多次,另外重构后可以便于后续开发。当然重构后使用before_action也是可以的,看个人需求。
打开app/controllers/application_controller.rb

app/controllers/application_controller.rb
1
2
3
4
5
6
7
8
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
+ before_action :fetch_home_data #这样写会增加数据查询量,后续更新
+ def fetch_home_data
+ @categories = Category.grouped_data
+ end

修改app/controllers/categories_controller.rb

app/controllers/categories_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
class CategoriesController < ApplicationController
def show
- @categories = Category.grouped_data
+ fetch_home_data
@category = Category.find(params[:id])
@products = @category.products.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
.order("id desc").includes(:main_product_image)
end
end

同时修改后台app/controllers/admin/base_controller.rb

app/controllers/admin/base_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Admin::BaseController < ActionController::Base
layout 'layouts/admin'
before_action :authenticate_user!
before_action :admin_required
before_action :fetch_home_data #这样写会增加数据查询量,后续更新
def admin_required
if !current_user.admin?
redirect_to "/", alert: "You are not admin."
end
end
def fetch_home_data
@categories = Category.grouped_data
end
end

Step 2:

打开app/controllers/products_controller.rb

app/controllers/products_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ProductsController < ApplicationController
def index
@products = Product.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
.order("id desc")
end
def show
@product = Product.find(params[:id])
end
def add_to_cart
@product = Product.find(params[:id])
if !current_cart.products.include?(@product)
current_cart.add_product_to_cart(@product)
flash[:notice] = "你已成功将 #{@product.title} 加入购物车"
else
flash[:warning] = "你的购物车内已有此物品"
end
redirect_to :back
end
end

这里onshelf用了重构,以便多次使用。
打开打开app/model/product.rb,加入onshelf这个scope。,加入onshelf这个scope。

app/model/product.rb
1
2
3
before_create :set_default_attrs #商品建立之前,先生成一个随机序列号
+ scope :onshelf, -> { where(status: Status::On) }

Step 3:(这个好像没有这样改)

app/views/products/index.html.erb

app/views/products/index.html.erb
1
2
3
4
5
6
7
8
<div class="row">
<div class="col-lg-12">
<ol class="breadcrumb">
<li class="active">首页</li>
</ol>
<%= render 'common/products' %>
</div>
</div>

Step 4:

touch app/views/common/_categories.html.erb
用来独立的放置商品类别,因为后面会在多个页面使用。

app/views/common/_categories.html.erb
1
2
3
4
5
6
7
8
<ul class="list-group">
<% @categories.each do |group| %>
<li class="list-group-item"><%= group.first.title %></li>
<% group.last.each do |sub_category| %>
<li class="list-group-item"><a href="<%= category_path(sub_category) %>"><%= sub_category.title %></a></li>
<% end -%>
<% end -%>
</ul>

Step 5:

touch app/views/common/_products.html.erb

app/views/common/_products.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<% @products.each do |product| %>
<div class="col-xs-6 col-md-3 text-center product-list-style">
<div>
<% if product.product_images.present? %>
<a href="<%= product_path(product) %>"> <img src=<%= qiniu_image_path(product.main_product_image.image.url) %>></a>
<% else %>
<%= link_to image_tag("http://placehold.it/200x200&text=No Pic", class: "product-list-style-img img-responsive center-block"), product_path(product), target: "_blank" %>
<% end %>
</div>
<br>
<%= link_to(product.title, product_path(product), target: "_blank") %>
<br>
<p><strong>¥ <%= link_to(product.price, product_path(product), target: "_blank") %> 元</strong></p>
</div>
<% end %>

请注意这里我在取出照片时,没有使用product.product_images.first.image.url(:middle),因为这样会再次查询数据库,而且是对所有图片进行检索,这是非常经典的N+1查询问题,会影响网站的性能。为了避免N+1查询,我们可以在welcome_controller.rb

打开app/controllers/products_controller.rb

app/controllers/products_controller.rb
1
2
3
4
def index
@products = Product.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
.order("id desc").includes(:product_images)
end

Step 6:

这样才第一次查询数据库时,会同时检索出产品的所有product_images。

但这样还是不太好,因为我们在商品迭代输出时并不需要商品的所以图片,仅需要一张即可。

在product.rb model中,我们建立产品的一对一关系 main_product_image

打开app/models/product.rb

app/models/product.rb
1
2
3
4
has_many :product_images, -> { order(weight: 'desc') },
dependent: :destroy
+ has_one :main_product_image, -> { order(weight: 'desc') },
class_name: :ProductImage

这样就可以找到该产品权重最高的一张图片了。

这时我们使用了product.main_product_image.image.url(:middle),

同时打开prodcuct controller中

app/controllers/products_controller.rb
1
2
3
4
5
class ProductsController < ApplicationController
def index
@products = Product.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
.order("id desc").includes(:main_product_image)
end

这样在第一次查询数据库时,会同时检索出产品的main_product_image,

###Step 7:建立路由

config/routes.rb
1
+ resources :categories, only: [:show]

关于在前端做个简单的样式定义:

app/assets/stylesheets/products.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/****** 商品列表简单样式 ******/
.list-group-item a {
display: block;
}
.msrp {
text-decoration: line-through;
}
.thumbnail {
height: 290px;
&.detail {
height: auto;
}
a.title {
color: #333;
}
}

Step 8: 美化商品展示页面

打开app/views/products/show.html.erb

app/views/products/show.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<div class="row">
<div class="col-lg-3">
<%= render 'common/categories' %>
</div>
<div class="col-lg-9">
<ol class="breadcrumb">
<li><a href="<%= root_path %>">所有宝贝</a></li>
<li class="active"><%= @product.category.parent.title %></li>
<li><a href="<%= category_path @product.category %>"><%= @product.category.title %></a></li>
<li class="active"><%= @product.title %></li>
</ol>
<div class="preview col-md-7 " style="">
<% @product.product_images.each do |product_image| %>
<div class="col-xs-6 col-md-6">
<a href="#" class="thumbnail detail">
<%= image_tag product_image.image.url %>
</a>
</div>
<% end %>
</div>
<div class="preview col-md-5 " style="">
<h1><%= @product.title %></h1>
<ul class="list-unstyled">
<li>商品编号: <%= @product.uuid %></li>
<li>库存: <%= @product.quantity %></li>
</ul>
<h4><span class="msrp">原价: ¥<%= @product.msrp %></span></h4>
<h3><strong>现价: ¥<%= @product.price %></strong> </h3>
<p>购买: <input type="text" name="quantity" value="1" />件</p>
<div class="pull-right">
<% if @product.quantity.present? && @product.quantity > 0 %>
<%= link_to("加入购物车", add_to_cart_product_path(@product), method: :post,
class: "btn btn-lg btn-danger") %>
<% else %>
已销售一空,无法购买
<% end %>
</div>
</div>
<br />
<br />
<ul class="nav nav-tabs">
<li role="presentation" class="active">
<a href="#tab_default_1" data-toggle="tab">宝贝详情</a>
</li>
<li>
<a href="#tab_default_2" data-toggle="tab">宝贝评价(<%= "0" %>) </a>
</li>
</ul>
<br />
<div class="tab-content">
<div class="tab-pane active" id="tab_default_1">
<p class="product-description"><%= @product.description.html_safe %></p>
<p class="product-description2 text-center">产品展示</p>
<div class="row">
<% @product.product_images.each do |product_image| %>
<div class="col-xs-12 col-md-12 product-show-detail">
<%= image_tag product_image.image.url %>
</div>
<% end %>
</div>
<div class="faq">
<p class="faq-title text-center">常见问题</p>
<p class="question">使用什么快递发货?</p>
<p class="answer">伴客默认使用顺丰快递发货,配送范围覆盖全国大部分地区(港澳台地区除外)。</p>
<p class="question">如何申请退货?</p>
<p class="answer">
1.自收到商品之日起7日内,顾客可申请无忧退货,退款将原路返还,不同的银行处理时间不同,预计1-5个工作日到账;<br />
2.退货流程:<br />
确认收货-申请退货-客服审核通过-用户寄回商品-仓库签收验货-退款审核-退款完成;<br />
3.因MONSOON产生的退货,如质量问题,退货邮费由MONSOON承担,退款完成后钱款将沿原渠道返还。因客户个人原因产生的退货,购买和寄回运费由客户个人承担。<br />
</p>
<p class="question">如何开具发票?</p>
<p class="answer">如需开具普通发票,请在下单时联系客服办理,我们将虽货物一起快递给您;</p>
</div>
</div>
</div>
</div>
</div>

Step 9:

git add .
git commit -m “完成分类和商品展示页面初步开发”
git push origin story14
git checkout master
git merge story14
git push origin master

解释

文章目录
  1. 1. 目标
  2. 2. 目标
  3. 3. 步骤
    1. 3.1. Step 0:建立新分支
    2. 3.2. Step 2:
    3. 3.3. Step 3:简单重构
    4. 3.4. Step 4:
    5. 3.5. Step 5:
    6. 3.6. Step 6:
    7. 3.7. Step 7:
    8. 3.8. Step 8:建立app/views/common/_categories.html.erb
    9. 3.9. Step 9:建立app/views/common/_products.html.erb
    10. 3.10. Step 10:
    11. 3.11. Step 11:建立路由
    12. 3.12. Step 12: 美化商品展示页面
    13. 3.13. Step 13:将变更 commit 进去 git 里面。
  4. 4. 本章详解
  5. 5. 步骤
    1. 5.1. Step 1:
    2. 5.2. Step 2:
    3. 5.3. Step 3:(这个好像没有这样改)
    4. 5.4. Step 4:
    5. 5.5. Step 5:
    6. 5.6. Step 6:
    7. 5.7. Step 8: 美化商品展示页面
    8. 5.8. Step 9:
  6. 6. 解释