Skip to content

Admin 生成器

zen:admin 生成完整的管理后台 CRUD 页面。

基本用法

bash
rails generate zen:admin Article title:string body:text status:enum --rich_text=body

模式

列表和表单在同一个页面,表单以 Modal 弹出。

bash
rails generate zen:admin Article --modal

适合字段较少(≤8 个)的模型。

Page 模式

列表和表单是独立页面,表单在单独 URL。

bash
rails generate zen:admin Article --page

适合字段较多或需要富文本编辑的模型。

选项

选项说明示例
--modalModal 模式(默认)--modal
--pagePage 模式--page
--rich_text富文本字段--rich_text=body
--image图片上传字段--image=cover
--file文件上传字段--file=attachment
--tags标签字段--tags=keywords
--enums枚举值 (JSON)--enums='{"status":["draft","published"]}'
--belongs-tobelongs_to 关联--belongs-to=category
--has-manyhas_many 关联--has-many=comments
--product产品形态--product=kanban

枚举处理

bash
rails generate zen:admin Article title:string status:enum \
  --enums='{"status":["draft","published","archived"]}'

生成器会自动在 Model 中添加 enum 声明(通过 DSL field :status, :enum)。

表关联

belongs_to 关联

假设文章属于分类(Category),先创建 Category,再创建 Article 并关联:

bash
# 第一步:创建主表(Category)
rails generate zen:admin Category name:string

# 第二步:创建关联表(Article),使用 --belongs-to 指定关联
rails generate zen:admin Article title:string body:text category_id:integer \
  --belongs-to=category

生成器会自动:

  1. Model 中添加 belongs_to :category
  2. Form 中渲染下拉选择框(从 Category 表读取数据)
  3. Controller 中加载关联数据传给前端
  4. Table 中显示关联字段名称(而非 ID)

has_many 关联

假设用户有多篇文章:

bash
# 先创建 User
rails generate zen:admin User name:string email:string

# 再创建 Article,关联到 User
rails generate zen:admin Article title:string user_id:integer \
  --belongs-to=user

多重关联

同时关联多个模型:

bash
rails generate zen:admin Article title:string body:text \
  user_id:integer category_id:integer \
  --belongs-to=user,category

关联后的手动调整

生成后需要手动补充:

1. Model 中添加 has_many

ruby
# app/models/user.rb
class User < ApplicationRecord
  include Zen::ModelDsl

  has_many :articles, dependent: :destroy
end

# app/models/category.rb
class Category < ApplicationRecord
  include Zen::ModelDsl

  has_many :articles, dependent: :destroy
end

2. Migration 添加外键索引

ruby
# db/migrate/xxx_add_index_to_articles.rb
class AddIndexToArticles < ActiveRecord::Migration[8.0]
  def change
    add_index :articles, :user_id
    add_index :articles, :category_id
  end
end

3. DSL 配置关联显示

ruby
# app/models/article.rb
class Article < ApplicationRecord
  include Zen::ModelDsl

  field :title, :string, required: true
  field :body, :rich_text
  field :status, :enum, values: %w[draft published archived]

  belongs_to :user
  belongs_to :category

  display do
    list do
      column :title, link: true
      column :category_name   # 显示关联名称
      column :user_name       # 显示关联名称
      column :status, badge: true
    end
    form do
      section "内容" do
        field :title, required: true
        field :body, as: :rich_text
        field :category_id, as: :select   # 关联下拉框
        field :user_id, as: :select       # 关联下拉框
      end
    end
  end

  # 辅助方法:获取关联名称
  def category_name
    category&.name || '-'
  end

  def user_name
    user&.name || '-'
  end
end

产品形态

bash
rails generate zen:admin Task title:string status:enum \
  --enums='{"status":["todo","done"]}' \
  --product=kanban

支持形态:crud(默认)、kanbancalendargallery

生成的 Controller

ruby
# app/controllers/admin/articles_controller.rb
module Admin
  class ArticlesController < AdminController
    before_action :set_article, only: [:show, :update, :destroy]

    def index
      @articles = policy_scope(Article)
      render inertia: "admin/articles/Index",
        props: zen_props(Article, articles: @articles.as_json)
    end

    def create
      @article = Article.new(article_params)
      authorize @article
      # ...
    end
  end
end

生成器自动集成 zen_props(DSL 元数据)、policy_scope(Pundit 权限)、authorize

分页、搜索、过滤

生成器自动集成 Pagy(分页)和 Ransack(搜索、过滤)。

DSL 配置

在 Model 中配置:

ruby
class User < ApplicationRecord
  include Zen::ModelDsl

  field :email, :string, required: true
  field :username, :string
  field :role, :enum, values: %w[admin editor viewer]

  display do
    list do
      paginate true                  # 启用分页
      searchable :email, :username    # 启用搜索
      filterable :role                # 启用过滤
      column :email
      column :username
      column :role, badge: true
    end
  end
end

生成代码说明

后端(Controller)

ruby
# app/controllers/admin/users_controller.rb
class Admin::UsersController < AdminController
  include Pagy::Method

  def index
    q = User.ransack(search_params)
    base_query = q.result(distinct: true)
    @pagy, users = pagy(:offset, base_query, **pagy_params)

    render inertia: "admin/users/Index",
      props: zen_props(User, users: users.as_json,
        pagination: {
          page: @pagy.page,
          per_page: @pagy.limit,
          total: @pagy.count,
          pages: @pagy.pages
        }
      )
  end

  private

  def search_params
    params[:q]&.permit(:email_cont, :username_cont, :role_eq)
  end

  def pagy_params
    {
      page: params[:page] || 1,
      limit: params[:per_page] || 20
    }
  end
end

前端(DslTable)

生成器生成的前端页面使用 DslTable 组件,自动读取 zen_meta 渲染表格、搜索、过滤、分页:

tsx
// app/frontend/pages/admin/users/Index.tsx
import { DslTable } from '@/modules/dsl/DslTable'

<DslTable
  meta={meta}                        // zen_meta(由 zen_props 自动注入)
  data={users}                       // 列表数据
  basePath="/admin/users"            // 资源路径
  serverSide                         // 服务端分页模式
  pagination={pagination}            // 分页元数据(page/per_page/total)
  onServerChange={(params) => {
    // 分页/搜索/过滤变化时,通过 Inertia 重新请求(保留页面状态)
    router.visit('/admin/users', {
      data: params,
      preserveState: true,
      preserveScroll: true,
    })
  }}
/>

提示DslTable 会根据 meta.display.list 自动渲染列、搜索框、过滤菜单。分页参数(pageper_page)和搜索参数(q[email_cont] 等)会通过 onServerChange 回调传给后端。

Ransack 谓词

谓词说明示例
_cont包含(模糊搜索)email_cont
_eq等于(精确匹配)role_eq
_gteq大于等于created_at_gteq
_lteq小于等于created_at_lteq
_in在列表中status_in

自定义

如果不使用 DSL,可手动在 Controller 中添加:

ruby
include Pagy::Method

def index
  q = Model.ransack(params[:q])
  @pagy, records = pagy(:offset, q.result, **pagy_params)
  # ...
end

添加菜单(必做)

生成器不会自动添加菜单项,需要手动配置。

  1. 打开 app/frontend/config/adminMenus.tsx
  2. menuRoutes.routes 数组中添加菜单项:
tsx
{
  path: '/admin/articles',
  name: '文章管理',
  icon: <FileTextOutlined />,
}
  1. 在文件顶部 import 对应图标

提示:不添加菜单项也能通过 URL 直接访问,但侧边栏不会显示入口。

自定义

生成后可直接编辑生成的文件。推荐方式:

  1. 修改 Model DSL → 页面自动更新
  2. 如需深度定制,直接编辑 .tsx 文件