美文网首页
商城迁移

商城迁移

作者: Jayzen | 来源:发表于2017-02-22 18:57 被阅读31次

    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
    

    相关文章

      网友评论

          本文标题:商城迁移

          本文链接:https://www.haomeiwen.com/subject/zlimwttx.html