08 管理平台开发
09 管理平台:分类管理开发
10 管理平台 商品管理
11 管理平台 商品图片管理
12 分类和商品页面开发
13 购物车功能开发
08 管理平台开发
去除默认的routes的设定
#config/application.rb
config.generators do |generator|
generator.skip_routes true
end
routes.rb中建立独立的admin路由
namespace :admin do
root "sessions#new"
resources :sessions
resources :categories
end
终端创建controller
rails g controller admin::sessions new #会自动生成routes,但是前面已经skip routes了
rails g controller admin::categories index new
针对controller层,为了代码看起来更有逻辑性,可以生成一个Admin::BaseController继承于ApplicationController,Admin::SessionsController和Admin::CategoriesController同时继承于这个类。
针对view层,建立views/admin文件夹,其中建立layouts目录,这是针对管理平台页面的布局,其中建立头文件_menu.html.erb文件和布局文件admin.html.erb文件。
_menu.html.erb代码如下:
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="<%= admin_root_path %>">管理平台</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="<%= admin_root_path %>">Home <span class="sr-only">(current)</span></a></li>
<li><a href="<%= admin_categories_path %>">分类</a></li>
</ul>
</div>
</div>
</nav>
admin.html.erb代码如下:
<!DOCTYPE html>
<html>
<head>
<title>管理平台 - 蛋人商城</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<%= csrf_meta_tags %>
<!--下面会新增admin这个css文件-->
<%= stylesheet_link_tag 'admin', media: 'all' %>
<%= yield :stylesheets %>
</head>
<body>
<%= render 'admin/layouts/menu' %>
<div class="container">
<% unless flash[:notice].blank? %>
<div class="alert alert-info"><%= flash[:notice] %></div>
<% end -%>
<%= yield %>
</div>
<!--下面会新增admin.js这个js文件-->
<%= javascript_include_tag 'admin' %>
<%= yield :javascripts %>
</body>
</html>
对父模板进行引用
#base_controller.rb
class Admin::BaseController < ActionController::Base
layout "admin/layouts/admin"
end
因为为后台添加了样式,所以针对这个新的样式添加新的css和js文件。
#app/assets/javascripts/admin.js
//= require jquery
//= require jquery_ujs
//= require bootstrap-sprockets
//= require_tree . #这句代码需删除,因为这句代码会把javascript中所有的js代码都加载进去,application.js中也删除该代码
##app/assets/javascripts/admin.scss
@import "bootstrap-sprockets";
@import "bootstrap";
@import "font-awesome";
因为会独立引用css和js文件,所以需要对预编译文件进行配置
#config/initializers/assets.rb
Rails.application.config.assets.precompile += %w(
admin.js
admin.css
)
09 管理平台:分类管理开发
category的model,controller,view层的开发,这里有一级分类和二级分类。
product.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
private
def correct_ancestry
self.ancestry = nil if self.ancestry.blank?
end
end
categories_controller.rb
class Admin::CategoriesController < Admin::BaseController
before_action :find_root_categories, only: [:new, :create, :edit, :update]
before_action :find_category, only: [:edit, :update, :destroy]
def index
if params[:id].blank?
@categories = Category.roots
else
@category = Category.find(params[:id])
@categories = @category.children
end
@categories = @categories.page(params[:page] || 1).per_page(params[:per_page] || 10)
.order(id: "desc")
end
def new
@category = Category.new
end
def create
@category = Category.new(params.require(:category).permit!)
if @category.save
flash[:notice] = "保存成功"
redirect_to admin_categories_path
else
render action: :new
end
end
def edit
render action: :new
end
def update
@category.attributes = params.require(:category).permit!
if @category.save
flash[:notice] = "修改成功"
redirect_to admin_categories_path
else
render action: :new
end
end
def destroy
if @category.destroy
flash[:notice] = "删除成功"
redirect_to admin_categories_path
else
flash[:notice] = "删除失败"
redirect_to :back
end
end
private
def find_root_categories
@root_categories = Category.roots.order(id: "desc")
end
def find_category
@category = Category.find(params[:id])
end
end
views/admin/categories/new.html.erb和index.html.erb
#new.html.erb
<div>
<h1><%= @category.new_record? ? "新建分类" : "修改分类 ##{params[:id]}" %></h1>
</div>
<div class="form-body">
<%= form_for @category,
url: (@category.new_record? ? admin_categories_path : admin_category_path(@category)),
method: (@category.new_record? ? 'post' : 'put'),
html: { class: 'form-horizontal' } do |f| %>
<% unless @category.errors.blank? %>
<div class="alert alert-danger">
<ul class="list-unstyled">
<% @category.errors.messages.values.flatten.each do |error| %>
<li><i class="fa fa-exclamation-circle"></i> <%= error %></li>
<% end -%>
</ul>
</div>
<% end -%>
<div class="form-group">
<label for="ancestry" class="col-sm-2 control-label">所属分类:</label>
<div class="col-sm-5">
<select name="category[ancestry]">
<option></option>
<% @root_categories.each do |category| %>
<!--不能为自己的父类-->
<% next if category == @category %>
<option value="<%= category.id %>" <%= @category.ancestry == category.id.to_s ? 'selected' : '' %>><%= category.title %></option>
<% end -%>
</select>
为空为一级分类
</div>
</div>
<div class="form-group">
<label for="title" class="col-sm-2 control-label">名称:*</label>
<div class="col-sm-5">
<%= f.text_field :title, class: "form-control" %>
</div>
</div>
<div class="form-group">
<label for="weight" class="col-sm-2 control-label">权重:</label>
<div class="col-sm-5">
<%= f.text_field :weight, class: "form-control" %> 数值越大越靠前
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
<%= f.submit (@category.new_record? ? "新建分类" : "编辑分类"), class: "btn btn-default" %>
</div>
</div>
<% end -%>
</div>
#index.html.erb
<div>
<div class="pull-right">
<%= link_to "新建分类", new_admin_category_path, class: "btn btn-primary" %>
</div>
<h1>
<% if @category %>
分类:<%= @category.title %>(<%= @categories.total_entries %>)
<% else %>
分类(<%= @categories.total_entries %>)
<% end -%>
</h1>
</div>
<div>
<table class="table table-striped">
<tr>
<th>ID</th>
<th>名称</th>
<th>Weight</th>
<th></th>
</tr>
<% @categories.each do |category| %>
<tr>
<td><%= category.id %></td>
<td><%= category.title %></td>
<td><%= category.weight %></td>
<td align="right">
<%= link_to "编辑", edit_admin_category_path(category) %>
<%= link_to "删除", admin_category_path(category), method: :delete, 'data-confirm': "确认删除吗?" %>
<% if category.root? %>
<%= link_to "查看子分类", admin_categories_path(id: category) %>
<% end -%>
</td>
</tr>
<% end -%>
</table>
<%= will_paginate @categories %>
</div>
10 管理平台 商品管理
product.rb模型描述
#product.rb
class Product < ApplicationRecord
validates :category_id, presence: { message: "分类不能为空" }
validates :title, presence: { message: "名称不能为空" }
validates :status, inclusion: { in: %w[on off],
message: "商品状态必须为on | off" }
validates :amount, numericality: { only_integer: true,
message: "库存必须为整数" },
if: proc { |product| !product.amount.blank? }
validates :amount, presence: { message: "库存不能为空" }
validates :msrp, presence: { message: "MSRP不能为空" }
validates :msrp, numericality: { message: "MSRP必须为数字" },
if: proc { |product| !product.msrp.blank? }
validates :price, numericality: { message: "价格必须为数字" },
if: proc { |product| !product.price.blank? }
validates :price, presence: { message: "价格不能为空" }
validates :description, presence: { message: "描述不能为空" }
belongs_to :category
before_create :set_default_attrs
module Status
On = 'on'
Off = 'off'
end
private
def set_default_attrs
self.uuid = RandomCode.generate_product_uuid
end
end
products_controller.rb描述
class Admin::ProductsController < Admin::BaseController
before_action :find_product, only: [:edit, :update, :destroy]
def index
@products = Product.page(params[:page] || 1).per_page(params[:per_page] || 10)
.order("id desc")
end
def new
@product = Product.new
@root_categories = Category.roots
end
def create
@product = Product.new(params.require(:product).permit!)
@root_categories = Category.roots
if @product.save
flash[:notice] = "创建成功"
redirect_to admin_products_path
else
render action: :new
end
end
def edit
@root_categories = Category.roots
render action: :new
end
def update
@product.attributes = params.require(:product).permit!
@root_categories = Category.roots
if @product.save
flash[:notice] = "修改成功"
redirect_to admin_products_path
else
render action: :new
end
end
def destroy
if @product.destroy
flash[:notice] = "删除成功"
redirect_to admin_products_path
else
flash[:notice] = "删除失败"
redirect_to :back
end
end
private
def find_product
@product = Product.find(params[:id])
end
end
设置常量的值
#product.rb
class Product < ApplicationRecord
module Status
On = 'on'
Off = 'off'
end
end
设置new.html.erb和index.html.erb
#index.html.erb
<div>
<div class="pull-right">
<%= link_to "新建商品", new_admin_product_path, class: "btn btn-primary" %>
</div>
<h1>
商品(<%= @products.total_entries %>)
</h1>
</div>
<div>
<table class="table table-striped">
<tr>
<th>ID</th>
<th>名称</th>
<th>UUID/SKU</th>
<th>MSRP</th>
<th>Price</th>
<th>库存</th>
<th>状态</th>
<th></th>
</tr>
<% @products.each do |product| %>
<tr>
<td><%= product.id %></td>
<td><%= product.title %></td>
<td><%= product.uuid %></td>
<td><%= product.msrp %></td>
<td><%= product.price %></td>
<td><%= product.amount %></td>
<td><%= product.status %></td>
<td align="right">
<%= link_to "编辑", edit_admin_product_path(product) %>
<%= link_to "删除", admin_product_path(product), method: :delete, 'data-confirm': "确认删除吗?" %>
</td>
</tr>
<% end -%>
</table>
<%= will_paginate @products %>
</div>
#new.html.erb
<div>
<h1><%= @product.new_record? ? "新建商品" : "修改商品 ##{params[:id]}" %></h1>
</div>
<div class="form-body">
<%= form_for @product,
url: (@product.new_record? ? admin_products_path : admin_product_path(@product)),
method: (@product.new_record? ? 'post' : 'put'),
html: { class: 'form-horizontal' } do |f| %>
<% unless @product.errors.blank? %>
<div class="alert alert-danger">
<ul class="list-unstyled">
<% @product.errors.messages.values.flatten.each do |error| %>
<li><i class="fa fa-exclamation-circle"></i> <%= error %></li>
<% end -%>
</ul>
</div>
<% end -%>
<div class="form-group">
<label for="title" class="col-sm-2 control-label">名称:*</label>
<div class="col-sm-5">
<%= f.text_field :title, class: "form-control" %>
</div>
</div>
<div class="form-group">
<label for="category_id" class="col-sm-2 control-label">所属分类:</label>
<div class="col-sm-5">
<select name="product[category_id]">
<option></option>
<% @root_categories.each do |category| %>
<optgroup label="<%= category.title %>"></optgroup>
<% category.children.each do |sub_category| %>
<option value="<%= sub_category.id %>" <%= @product.category_id == sub_category.id ? 'selected' : '' %>><%= sub_category.title %></option>
<% end -%>
<% end -%>
</select>
</div>
</div>
<div class="form-group">
<label for="title" class="col-sm-2 control-label">上下架状态:*</label>
<div class="col-sm-5">
<select name="product[status]">
<% [[Product::Status::On, '上架'], [Product::Status::Off, '下架']].each do |row| %>
<option value="<%= row.first %>" <%= 'selected' if @product.status == row.first %>><%= row.last %></option>
<% end -%>
</select>
</div>
</div>
<div class="form-group">
<label for="amount" class="col-sm-2 control-label">库存*:</label>
<div class="col-sm-5">
<%= f.text_field :amount, class: "form-control" %> 必须为整数
</div>
</div>
<div class="form-group">
<label for="price" class="col-sm-2 control-label">价格*:</label>
<div class="col-sm-5">
<%= f.text_field :price, class: "form-control" %>
</div>
</div>
<div class="form-group">
<label for="msrp" class="col-sm-2 control-label">MSRP*:</label>
<div class="col-sm-5">
<%= f.text_field :msrp, class: "form-control" %>
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">描述*:</label>
<div class="col-sm-5">
<%= f.text_area :description, class: "form-control" %>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
<%= f.submit (@product.new_record? ? "新建商品" : "编辑商品"), class: "btn btn-default" %>
</div>
</div>
<% end -%>
</div>
11 管理平台 商品图片管理
参考peperclip这个gem进行代码填充,和这个gem代码唯一的不同是,这个gem中的代码没有添加product_image与product的关联关系。如果要应用到这个项目中,需要添加这两个model的关联关系,同时修改controller和view中的代码。
12 分类和商品页面开发
这个章节需要处理
1、左边标签栏的创建
2、右边面包屑以及图片成列
3、N+1问题的处理
对于标签的左边栏显示参考ancestry的生成一二级标签栏页面的代码。
利用scope查询只有上架的商品被显示
#routes.rb
resources :categories, only: [:show]
resources :products, only: [:show]
#product.rb
class Product < ApplicationRecord
scope :onshelf, ->{ where(status: Status::On) }
end
#welcome_controller.rb
class WelcomeController < ApplicationController
def index
fetch_home_data #已经在ApplicationController类中被定义
@products = Product.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
.order("id desc").includes(:main_product_image)
end
end
#app/views/welcome/index.html.erb,使用两个嵌套模板
<div class="row">
<div class="col-lg-3">
<%= render 'shared/categories' %>
</div>
<div class="col-lg-9">
<ol class="breadcrumb">
<li class="active">Home</li>
</ol>
<%= render 'shared/products' %>
</div>
</div>
#app/views/shared/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>
#app/views/shared/products.html.erb
<div class="row">
<% @products.each do |product| %>
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<%= link_to image_tag(product.main_product_image.image.url(:middle), alt: product.title), product_path(product) %>
<div class="caption">
<h4><%= link_to product.title, product_path(product), class: 'title' %></h4>
<p><strong>¥<%= product.price %></strong> <span class="msrp">¥<%= product.msrp %></span></p>
<p><a href="#" class="btn btn-danger btn-sm" role="button">加入购物车</a></p>
</div>
</div>
</div>
<% end -%>
</div>
<%= will_paginate @products %>
通过product.main_product和includes解决N+1问题
#product.rb
class Product < ApplicationRecord
has_one :main_product_image, -> { order(weight: 'desc') },
class_name: :ProductImage
end
#welcome_controller.rb
class WelcomeController < ApplicationController
def index
fetch_home_data #已经在ApplicationController类中被定义
@products = Product.onshelf.page(params[:page] || 1).per_page(params[:per_page] || 12)
.order("id desc").includes(:main_product_image)
end
end
添加相关样式#stylesheets/home.css,需要在application.scss文件中进行import
.list-group-item a {
display: block;
}
.msrp {
text-decoration: line-through;
}
.thumbnail {
height: 290px;
&.detail {
height: auto;
}
a.title {
color: #333;
}
}
点击左边标签,显示该标签下面的商品
#categories_controller.rb
class CategoriesController < ApplicationController
def show
@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
<div class="row">
<div class="col-lg-3">
<%= render 'shared/categories' %>
</div>
<div class="col-lg-9">
<ol class="breadcrumb">
<li><a href="<%= root_path %>">Home</a></li>
<li class="active"><%= @category.parent.title %></li>
<li class="active"><%= @category.title %></li>
</ol>
<h1><%= @category.title %></h1>
<%= render 'shared/products' %>
</div>
</div>
点击商品,查看商品的详细信息
class ProductsController < ApplicationController
def show
fetch_home_data
@product = Product.find(params[:id])
end
end
<div class="row">
<div class="col-lg-3">
<%= render 'shared/categories' %>
</div>
<div class="col-lg-9">
<ol class="breadcrumb">
<li><a href="<%= root_path %>">Home</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>
<h1><%= @product.title %></h1>
<div class="row">
<% @product.product_images.each do |product_image| %>
<div class="col-xs-6 col-md-3">
<a href="#" class="thumbnail detail">
<%= image_tag product_image.image.url(:middle) %>
</a>
</div>
<% end -%>
</div>
<ul class="list-unstyled">
<li>商品编号: <%= @product.uuid %></li>
<li>库存: <%= @product.amount %></li>
</ul>
<h3><strong>¥<%= @product.price %></strong> <span class="msrp">¥<%= @product.msrp %></span></h3>
<p><%= link_to "加入购物车", "#", class: "btn btn-danger" %></p>
<br />
<br />
<ul class="nav nav-tabs">
<li role="presentation" class="active"><a href="javascript:;">商品描述</a></li>
</ul>
<br />
<div>
<%= @product.description.html_safe %>
</div>
</div>
</div>
13 购物车功能开发
场景描述和解释:
因为在不同的电脑上都可以查看到用户的购物车,因此这个购物车到存储到数据库中。正常情况下,这个购物车需要指定属于哪个用户,所以需要user_id这个字段(允许为空),但是也可以在用户不登录的情况下创建购物车,所以需要user_uuid这个字段,用来追踪这个购物车属于哪个用户,[这里采取的策略是,任何一个用户打开这个网站的时候,都在浏览器的cookies中设置一个user_uuid(唯一字符串),当用户添加购物车的时候,将使用这个user_uuid来追踪当前的用户],当用户进行注册的时候,则将user_uuid绑定到用户创建这个用户表中来,如果是要进行登录,那么将用户表中本身存在user_uuid值取出,然后更新存储于浏览器的cookies中的user_uuid值。这样保证了用户是否登录,user_uuid追踪用户都是有效的。
ShoppingCart模型创建和User模型修改
#创建ShoppingCart模型
class CreateShoppingCarts < ActiveRecord::Migration[5.0]
def change
create_table :shopping_carts do |t|
t.integer :user_id
t.string :user_uuid
t.integer :product_id
t.integer :amount
t.timestamps
end
add_index :shopping_carts, [:user_id]
add_index :shopping_carts, [:user_uuid]
end
end
#修改User模型
class AddUserUuidColumn < ActiveRecord::Migration[5.0]
def change
add_column :users, :uuid, :string
add_index :users, [:uuid], unique: true
User.find_each do |user|
user.uuid = RandomCode.generate_utoken
user.save
end
end
end
用户未登录下购物车功能的实现
思路:整个购物车的实现主要是通过user_uuid和uuid两个字段来实现的。不管是登录还是注册,将用户的uuid和浏览器中cookies中的user_uuid进行绑定即可。
1、任何一个用户打开这个网站的时候,都在浏览器的cookies中设置一个user_uuid(唯一字符串)
#application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :set_browser_uuid
protected
def set_browser_uuid
uuid = cookies[:user_uuid]
unless uuid
if logged_in?
uuid = current_user.uuid
else
uuid = RandomCode.generate_utoken
end
end
update_browser_uuid uuid
end
def update_browser_uuid uuid
#添加cookies是为了长久保持在浏览器当中
session[:user_uuid] = cookies.permanent['user_uuid'] = uuid
end
end
2、用户注册的时候,需要将浏览器中user_uuid绑定到用户中,当用户进行登录,那么将用户表中本身存在user_uuid值取出,然后更新存储于浏览器的cookies中的user_uuid值。
#users_controller.rb,用户注册的情况,存在于create方法中
@user.uuid = session[:user_uuid]
#sessions_controller.rb,用户登录的情况下,参考application_controller.rb文件中方法
update_browser_uuid user.uuid
3、购物车添加、删除和更新功能实现
#routes.rb
resources :shopping_carts
#shopping_carts_controller.rb
class ShoppingCartsController < ApplicationController
def index
@
end
end
网友评论