ruby on rails h5/web 2套 theme

如何使用 rails 生成 h5/web 2套 theme

太好了!你选择的是 方案一:基于 User-Agent 自动判断设备类型,并加载两套完全独立的 View,这是一个非常清晰且可维护的方式,尤其适合 H5 和 Web 界面差异较大的场景。

下面我会为你详细规划整个 Rails 项目的结构、逻辑流程和最佳实践,确保两套视图完全独立、互不干扰,同时保持业务逻辑的 DRY(不重复)。


✅ 目标

  • 同一个控制器处理逻辑
  • 根据设备自动选择:
    • H5 主题 → 使用 views/h5/... 下的视图
    • Web 主题 → 使用 views/web/... 下的视图
  • 布局、样式、视图全部独立
  • 控制器复用业务逻辑(如查询数据)

🗂 项目结构规划

app/
├── controllers/
   └── posts_controller.rb          # 共用控制器
├── views/
   ├── h5/                          # H5 主题视图
      ├── posts/
         ├── index.html.erb
         ├── show.html.erb
         └── _post.html.erb
      └── layouts/
          └── application.html.erb
   ├── web/                         # Web 主题视图
      ├── posts/
         ├── index.html.erb
         ├── show.html.erb
         └── _post.html.erb
      └── layouts/
          └── application.html.erb
   └── shared/                      # 可选:共享 partial
       └── _analytics.html.erb
├── helpers/
   ├── h5_helper.rb
   ├── web_helper.rb
   └── application_helper.rb
└── assets/
    ├── stylesheets/
       ├── h5.scss                  # @import "h5/**/*.scss"
       ├── web.scss
       ├── h5/
          └── posts.scss
       └── web/
           └── posts.scss
    └── javascript/
        ├── h5.js
        └── web.js

🔧 步骤 1:添加 browser gem(用于设备检测)

# Gemfile
gem 'browser', '~> 4.0'
bundle install

🔧 步骤 2:在 ApplicationController 中判断设备并设置视图路径

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_view_path

  private

  def set_view_path
    if browser.device.mobile? || browser.device.tablet?
      prepend_view_path 'app/views/h5'
      @current_theme = :h5
    else
      prepend_view_path 'app/views/web'
      @current_theme = :web
    end

    # 可选:存入 Thread 或实例变量供 helper 使用
    @device_type = @current_theme
  end
end

prepend_view_path 是关键:它会优先查找 h5/web/ 目录下的视图。


🔧 步骤 3:创建两套独立的布局文件

H5 布局

<!-- app/views/h5/layouts/application.html.erb -->
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>H5主题 - <%= yield(:title) %></title>
  <%= stylesheet_link_tag 'h5' %>
  <%= javascript_include_tag 'h5' %>
  <%= csrf_meta_tags %>
</head>
<body class="h5-body">
  <header>📱 H5 Header</header>
  <main><%= yield %></main>
  <footer>© H5 Theme</footer>
</body>
</html>

Web 布局

<!-- app/views/web/layouts/application.html.erb -->
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>Web主题 - <%= yield(:title) %></title>
  <%= stylesheet_link_tag 'web' %>
  <%= javascript_include_tag 'web' %>
  <%= csrf_meta_tags %>
</head>
<body class="web-body">
  <header>🖥️ Web Header</header>
  <nav>导航栏...</nav>
  <main><%= yield %></main>
  <footer>© Web Theme</footer>
</body>
</html>

🔧 步骤 4:创建控制器(复用逻辑)

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.published.order(created_at: :desc)
  end

  def show
    @post = Post.find(params[:id])
  end

  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to post_path(@post), notice: '发布成功'
    else
      render :new
    end
  end

  private

  def post_params
    params.require(:post).permit(:title, :body)
  end
end

✅ 控制器无需关心视图,Rails 会自动去 h5/posts/web/posts/ 找对应模板。


🔧 步骤 5:创建两套视图

H5 视图示例

<!-- app/views/h5/posts/index.html.erb -->
<div class="h5-post-list">
  <% @posts.each do |post| %>
    <a href="<%= post_path(post) %>">
      <h3><%= post.title %></h3>
    </a>
  <% end %>
</div>

Web 视图示例

<!-- app/views/web/posts/index.html.erb -->
<div class="web-post-grid">
  <h1>所有文章</h1>
  <% @posts.each do |post| %>
    <article class="card">
      <h2><%= link_to post.title, post_path(post) %></h2>
      <p><%= truncate(post.body, length: 100) %></p>
    </article>
  <% end %>
</div>

🔧 步骤 6:静态资源分离(CSS/JS)

编译独立的 CSS 和 JS

// app/assets/stylesheets/h5.scss
/*
 *= require_self
 *= require_tree ./h5
 */
// app/assets/stylesheets/web.scss
/*
 *= require_self
 *= require_tree ./web
 */
// app/assets/javascripts/h5.js
//= require_self
//= require_tree ./h5
console.log("H5 JS loaded");

// web.js 同理

🔧 步骤 7:Helper 分离(可选)

# app/helpers/h5_helper.rb
module H5Helper
  def h5_button(text)
    content_tag :button, text, class: 'h5-btn'
  end
end
# app/helpers/web_helper.rb
module WebHelper
  def web_button(text)
    content_tag :button, text, class: 'web-btn primary'
  end
end

在 layout 中自动加载(Rails 会根据 layout 自动 include 对应 helper)。


✅ 验证流程

  1. 用手机访问 /posts → 自动加载 h5/posts/index.html.erb + h5 布局
  2. 用电脑访问 /posts → 自动加载 web/posts/index.html.erb + web 布局
  3. 控制器逻辑完全复用
  4. 样式和脚本独立加载

🧪 测试设备判断(开发时)

你可以临时伪造 User-Agent 测试:

方法 1:浏览器插件

  • 安装 "User-Agent Switcher" 插件(Chrome/Firefox)
  • 切换为 iPhone 或 Android User-Agent

方法 2:curl 测试

curl -H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)" http://localhost:3000/posts

✅ 优点总结

| 优势 | 说明 |
|------|------|
| ✅ 完全独立视图 | H5 和 Web 可以完全不同,互不影响 |
| ✅ 逻辑复用 | 控制器、模型、helpers 可共享 |
| ✅ 易维护 | 结构清晰,按主题组织 |
| ✅ 自动适配 | 用户无感知,体验好 |


🚀 进阶建议

  • 加入 session[:force_theme] 支持用户手动覆盖(如“切换到电脑版”链接)
  • 使用 respond_to + format.mobile?(需 responders gem)作为替代方案
  • 如果用 Rails API + 前端框架,建议拆分为两个前端项目(React/Vue H5 vs Web)

需要我生成一个最小可运行的 GitHub 项目结构或代码模板吗?可以一键克隆的那种 😄

欢迎继续提问!