ActiveAdmin 是一款基于 Rails 的声明式后台管理框架,能够让大家写后台管理系统时会心一笑,下面是我司在生产中一些定制。
ActiveAdmin 默认主题是灰色的,巨丑。为此,使用他人开源的三个主题包,让客服姐姐和程序员哥哥按照个人喜好选用不同主题。管理员表需加个主题字段
# Gemfile
gem 'active_admin_theme' # 上下布局主题,比较实用,根据README说明安装,下同
gem 'active_material', '1.4.2' # 上下布局主题,支持移动端,Material Design风格;由于列表页的动作会隐藏,使得这个主题在我司用的人最少
gem 'arctic_admin', '3.0.0' # 左右布局主题,简约风,支持移动端
// app/assets/stylesheets/active_admin_theme.scss active_admin_theme这个主题不定制还是会有点丑
$skinMainSecondColor: #606ef0!default;
@import "active_admin/mixins";
@import "active_admin/base";
.site_title {
color: white!important;
}
select {
height: 29px;
margin: 0;
background-color: white;
border: 1px solid #e4eaec;
}
a {
border-radius: initial!important;
}
table.index_table tr.even td {
background-color: white;
}
# config/initializers/active_admin.rb
config.register_stylesheet 'active_admin_theme.css'
config.register_stylesheet 'active_material_theme.css'
config.register_stylesheet 'arctic_admin_theme.css'
config.register_javascript 'active_admin_theme.js'
config.register_javascript 'active_material_theme.js'
config.register_javascript 'arctic_admin_theme.js'
class ActiveAdmin::Views::Pages::Base < Arbre::HTML::Document
def build_active_admin_head
within head do
html_title [title, helpers.active_admin_namespace.site_title(self)].compact.join(' | ')
active_admin_namespace.meta_tags.each do |name, content|
text_node(tag(:meta, name: name, content: content))
end
case current_admin_user.theme
when 'active_material'
text_node stylesheet_link_tag('active_material_theme.css')
text_node javascript_include_tag('active_material_theme.js')
when 'arctic_admin'
text_node stylesheet_link_tag('arctic_admin_theme.css')
text_node javascript_include_tag('arctic_admin_theme.js')
else
text_node stylesheet_link_tag('active_admin_theme.css')
text_node javascript_include_tag('active_admin_theme.js')
end
if active_admin_namespace.favicon
text_node(favicon_link_tag(active_admin_namespace.favicon))
end
text_node csrf_meta_tag
end
end
end
# app/controllers/admin/home_controller.rb, config/routes.rb
get '/admin/check_theme', to: 'admin/home#check_theme', as: :check_theme
# 切换主题
def check_theme
theme = case current_admin_user.theme
when 'active_admin_theme' then 'arctic_admin'
when 'arctic_admin' then 'active_material'
when 'active_material' then 'active_admin_theme'
else
'active_admin_theme'
end
current_admin_user.update_columns(theme: theme)
redirect_back fallback_location: root_path
end
ActiveAdmin 字符串类型的筛选框默认是模糊匹配,即用 like‘%?%’,这不符合索引优化原则,故改为完全匹配
# config/initializers/active_admin.rb 末尾添加
ActiveAdmin::Inputs::Filters::StringInput.filters.clear
ActiveAdmin::Inputs::Filters::StringInput.filter(:equals, :contains, :starts_with, :ends_with)
ActiveAdmin 进入列表页时外键对应的 select 条件框会加载所有关联的 ID,数据表如果有上百万行就会卡死,改为默认关闭
# config/initializers/active_admin.rb
config.include_default_association_filters = false
默认情况下,每一列都会生成排序按钮,如果某列没有索引,排序会很慢,改为默认关闭,要用时手动开启
# config/initializers/active_admin.rb
class ActiveAdmin::Views::Pages::TableFor
class Column
def sortable?
return false
end
end
end
搜索条件如果输多了空格,客服姐姐就会来找你麻烦,默认帮她们去掉
class ApplicationController < ActionController::Base
before_action :strip_params, only: :index
def strip_params
return if params[:q].blank?
params[:q].each do |k, v|
params[:q][k] = v.strip
end
end
end
内部系统,别人要有管理员账号和密码才能黑进来,做不做强参数无所谓,关闭默认的强参数机制,省一些容易出错代码
class ApplicationController < ActionController::Base
before_action do
params.permit!
end
end
提取对于通用的 column 操作,避免代码重复(具体定义根据自己需求和代码来,以下是示例)
# config/initializers/active_admin.rb
class ActiveAdmin::Views::Pages::TableFor
# 昵称列,通过user_id在缓存中搜索昵称, 避免关联用户表
def user_column(attribute)
column('用户') do |model|
user_id = model.send(attribute)
link_to User.nick_name(user_id), admin_user_path(user_id)
end
end
# 手机列,通过user_id在缓存中搜索手机, 避免关联用户表
def phone_column(attribute)
column('手机号') do |model|
user_id = model.send(attribute)
link_to User.phone(user_id), admin_user_path(user_id)
end
end
# 数据库存储的数字,布尔列
def bool_column(attribute)
column(attribute) { |model| model.send(attribute).to_i == 1 ? status_tag('yes') : status_tag('no') }
end
# 图片列
def image_column(attribute, options = {})
column(attribute) do |model|
url = model.send(attribute)
image_tag(url, options) if url.present?
end
end
# 枚举值
def enum_column(attribute, alias_attribute = nil)
column(attribute) do |model|
enums = BaseValue.send(alias_attribute || attribute)
enums[model.send(attribute)]
end
end
# 时间戳转时间显示
def time_column(attribute)
column(attribute) do |model|
Time.at(model.send(attribute).to_i)
end
end
end
提取通用的 row 操作,避免代码重复
# config/initializers/active_admin.rb
class ActiveAdmin::Views::Pages::AttributesTable
def user_row(attribute)
row('用户') do |model|
user_id = model.send(attribute)
link_to User.nick_name(user_id), admin_user_path(user_id)
end
end
def phone_row(attribute)
row('手机号') do |model|
user_id = model.send(attribute)
link_to User.phone(user_id), admin_user_path(user_id)
end
end
def bool_row(attribute)
row(attribute) { |model| model.send(attribute) == 1 ? status_tag('yes') : status_tag('no') }
end
# 调用示例 image_row :avatar_qnniu, size: '50', style: 'border-radius: 50%'
def image_row(attribute, options = {})
row(attribute) do |model|
url = model.send(attribute)
image_tag(url, options) if url.present?
end
end
# 枚举值
def enum_row(attribute, alias_attribute = nil)
row(attribute) do |model|
enums = BaseValue.send(alias_attribute || attribute)
enums[model.send(attribute)]
end
end
def time_row(attribute)
row(attribute) do |model|
Time.at(model.send(attribute).to_i)
end
end
end
不同资源的列表页,当用 user_id 搜索时,都需要显示用户相关的一些按钮,可以提取成一个 helper 类,避免重复定义
# app/controllers/concerns/show_user_helper.rb
module ShowUserHelper
def self.included(dsl)
dsl.action_item(:show_user, only: [:index]) do
user_id = params.dig(:q, :user_id_equals) || params[:user_id]
resource = User.find_by(user_id: user_id) if user_id.present?
if resource.present?
# ......省略好些个按钮
if authorized?(:view_operates, resource)
a '日志', href: admin_operates_path(q: { user_id_equals: resource.user_id })
end
if authorized?(:generate_temp_password, resource)
a '生成临时密码', href: generate_temp_password_admin_user_path(resource), 'data-method': :post
end
end
end
end
end
ActiveAdmin.register_page 'Operates' do
menu label: '日志管理', parent: '系统管理', priority: 54
include ShowUserHelper # 包含helper, 这样上面那些按钮都有了
end
# config/initializers/active_admin.rb
config.footer = ->(_footer) { MyFooter.build(request) }
# config/initializers/my_footer.rb
class MyFooter < ActiveAdmin::Component
def self.build(request)
new.build(request)
end
def build(request)
div do
a('Change Language', href: '/admin/check_locale')
span '|'
a('Change Theme', href: '/admin/check_theme')
end
end
end
# 对应model代码里定义实例方法
def display_name
phone || user_id
end
上面这些实战,主要是通过打开类,修改 ActiveAdmin 源代码的方式操作的。