Skip to content
lanrion edited this page Apr 19, 2014 · 5 revisions

自定义菜单的实现

此实现测试通过的条件:

  • gem 'rails', '4.0.2'
  • gem 'jbuilder', '~> 1.2'

数据库设计:

class CreateDiymenus < ActiveRecord::Migration
  def change
    create_table :diymenus do |t|
      t.integer :public_account_id
      t.integer :parent_id # 所属父级菜单,如果当前是父级菜单,则此值为空
      t.string :name
      t.string :key  
      t.string :url
      t.boolean :is_show # 是否显示
      t.integer :sort    # 排序功能

      t.timestamps
    end
    add_index :diymenus, :public_account_id
    add_index :diymenus, :parent_id
    add_index :diymenus, :key

  end
end

Diymenu model 层设计

class Diymenu < ActiveRecord::Base

  CLICK_TYPE = "click" # key
  VIEW_TYPE  = "view"  # url

  belongs_to :public_account

  has_many :sub_menus, ->{where(is_show: true).order("sort").limit(5)}, class_name: "Diymenu", foreign_key: :parent_id

  def has_sub_menu?
    sub_menus.present?
  end

  # 优先为 click 类型
  def type
    key.present? ? CLICK_TYPE : VIEW_TYPE
  end

  def button_type(jbuilder)
    is_view_type? ? (jbuilder.url url) : (jbuilder.key key)
  end

  def is_view_type?
    type == VIEW_TYPE
  end
end

PublicAccount Model 设计

class PublicAccount < ActiveRecord::Base

# 自定义菜单
has_many :diymenus, dependent: :destroy

# 当前公众账号的所有父级菜单
has_many :parent_menus, ->{includes(:sub_menus).where(parent_id: nil, is_show: true).order("sort").limit(3)}, class_name: "Diymenu", foreign_key: :public_account_id

  def build_menu
    Jbuilder.encode do |json|
      json.button (parent_menus) do |menu|
        json.name menu.name
        if menu.has_sub_menu?
          json.sub_button(menu.sub_menus) do |sub_menu|
            json.type sub_menu.type
            json.name sub_menu.name
            sub_menu.button_type(json)
          end
        else
          json.type menu.type
          menu.button_type(json)
        end
      end
    end
  end
end

Action层的用法

# 结合: https://github.com/lanrion/weixin_authorize(建议选用此gem的Redis存access_token方案)
def generate_menu
  weixin_client = WeixinAuthorize::Client.new(@current_public_account.app_key, @current_public_account.app_secret)
  menu   = @current_public_account.build_menu
  result = weixin_client.create_menu(menu)
  set_error_message(result["errmsg"]) if result["errcode"] != 0
  redirect_to public_account_diymenus_path(@current_public_account)
end

需要存储公众账号的实现

Action 层:

if $weixin_authorize.is_valid?
  build_result = $weixin_authorize.create_menu(Diymenu.build_menu)
  msg = "build menu successfully." if build_result.ok?
else
  msg = "invalid appid or appsecret."
end
redirect_to admin_diymenus_path, notice: msg

Model层

class Diymenu < ActiveRecord::Base

  validates_uniqueness_of :name

  CLICK_TYPE = "click".freeze # key
  VIEW_TYPE  = "view".freeze  # url

  has_many :sub_menus, ->{where(is_show: true).order("sort").limit(5)}, class_name: "Diymenu", foreign_key: :parent_id

  belongs_to :parent, foreign_key: :parent_id, class_name: "Diymenu"

  def has_sub_menu?
    sub_menus.present?
  end

  # 优先为 click 类型
  def type
    key.present? ? CLICK_TYPE : VIEW_TYPE
  end

  def button_type(jbuilder)
    is_view_type? ? (jbuilder.url url) : (jbuilder.key key)
  end

  def is_view_type?
    type == VIEW_TYPE
  end

  class << self

    def parent_selection(current_menu)
      where.not(id: current_menu.id).map{|menu| [menu.name, menu.id]}
    end

    def parent_menus
      includes(:sub_menus).where(parent_id: nil, is_show: true).order("sort").limit(3)
    end

    def build_menu
      Jbuilder.encode do |json|
        json.button (parent_menus) do |menu|
          json.name menu.name
          if menu.has_sub_menu?
            json.sub_button(menu.sub_menus) do |sub_menu|
              json.type sub_menu.type
              json.name sub_menu.name
              sub_menu.button_type(json)
            end
          else
            json.type menu.type
            menu.button_type(json)
          end
        end
      end
    end
  end

end