Rails-treasure chest4: 使用图表对资料进行分析chart.js(及其他);管理用户权限的gem 'Pumdit'(6000🌟)

多档案上传
图表资料分析  Chartkick gem或者 chart.js
用户权限控管  gem Pundit (6000✨)

* HTML E-mail 寄送 : gem premailer-rails 

 


 上传图片

使用gem ''CarrierWave'

Rails5.1 有了内建的ActiveStorage。更好使。这里只是了解一下老版本的上传方法。

CarrierWave的特点是,model需要添加一个string格式的column来储存image信息。

而ActiveStorage,建立了2个Model,和需要图片或文件的model关联。



 

Chart


 

HTML Canvas (点击见API)

HTML<canvas>元素用于在网页上画图,它是一个container。实际上必须使用script来画图(通常是JavaScript)。

getContext()返回一个对象,这个对象提供方法和特性来支持在canvas上画图。

getContext("2d")对象,可以用于画text, lines, boxes, circles等。

例子:

fillStyle():填充color, gradient ,pattern

fillRect(x, y, width, height): 画出一个长方形的空间

 


 

制作图表需要用到前端 JavaScript 套件

  • Chartkick 这一套搭配 Rails 最简单(4800🌟),甚至不需要写 JavaScript 代码,有 Helper 可以使用
  • Chart.js 这一套好用又漂亮,需要写 JavaScript
  • D3.js 这一套功能最强,是专业的数据可视化套件,但也比较复杂

示范会使用 Chart.js,这也有中文文档

 

Chart.js

Chart.js 有 chart-js-rails gem 可以安装,但是因为只有后台报表那一页有用到,所以我们就不包进 asset pipeline 了,而是使用 CDN 让用户直接从 CDN 服务器下载回去。

 

在国内免费的 CDN 服务器 BootCDN 上可以找到 Chart.js,编辑 app/views/admin/events/show.html.erb 把该位置贴上去:

app/views/admin/events/show.html.erb

+  <script src="//cdn.bootcss.com/Chart.js/2.5.0/Chart.bundle.min.js"></script+  <canvas id="myChart" width="400" height="200"></canvas>+  <script>
+  var ctx = document.getElementById("myChart");
+  var myChart = new Chart(ctx, {
+      type: 'bar',  #产生柱状图,还有"line"产生线图,等等
+      data: {
+          labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
+          datasets: [{
+              label: '# of Votes',
+              data: [12, 19, 3, 5, 2, 3],
+              backgroundColor: [
+                  'rgba(255, 99, 132, 0.2)',
+                  'rgba(54, 162, 235, 0.2)',
+                  'rgba(255, 206, 86, 0.2)',
+                  'rgba(75, 192, 192, 0.2)',
+                  'rgba(153, 102, 255, 0.2)',
+                  'rgba(255, 159, 64, 0.2)'
+              ],
+              borderColor: [
+                  'rgba(255,99,132,1)',
+                  'rgba(54, 162, 235, 1)',
+                  'rgba(255, 206, 86, 1)',
+                  'rgba(75, 192, 192, 1)',
+                  'rgba(153, 102, 255, 1)',
+                  'rgba(255, 159, 64, 1)'
+              ],
+              borderWidth: 1
+          }]
+      },
+      options: {
+          scales: {
+              yAxes: [{
+                  ticks: {
+                      beginAtZero:true
+                  }
+              }]
+          }
+      }
+  });
+  </script>

data: 实际应当写成data: <%= @data.to_json%>。从controller中得到@data数据,然后用to_json转化为javascript变量。例子,在控制器的show方法:

+   status_colors = { "confirmed" => "#FF6384",
                      "pending" => "#36A2EB"}

+   @data1 = {
+       labels: ticket_names,
+       datasets: Registration::STATUS.map do |s|
+         {
+           label: I18n.t(s, :scope => "registration.status"),
+           data: @event.tickets.map{ |t| t.registrations.by_status(s).count },
+           backgroundColor: status_colors[s],
+           borderWidth: 1
+         }
+       end
+   }

labels: ticket_names将产生:

"labels": ["Guest","VIP 第一期","VIP 第二期"],  // 这是 x 轴的标籤

datasets: [{}] 参数其实就是资料集的数组,这里使用循环会产生2个资料hash。

    "datasets":[
      { "label": "报名尚未完成",     // 这是第一个资料集

        "data": [160,164,160],
        "backgroundColor":"#36A2EB",
        "borderWidth":1
      },
      { "label": "报名成功",         // 这是第二个资料集

        "data":[164,182,170],
        "backgroundColor":"#FF6384",
        "borderWidth":1
      }

 

 

 分析每日报名人数,并根据状态分类

 

分析从第一天到今天的每日报名人数,并根据状态分类。

 

 

编辑 app/controllers/admin/events_controller.rb。其中 labels 参数是 Y 轴,这里改成用 dates 数组,代表从有人报名的第一天到今天。

app/controllers/admin/events_controller.rb

+   if @event.registrations.any?
+     dates = (@event.registrations.order("id ASC").first.created_at.to_date..Date.today).to_a

+     @data3 = {
+       labels: dates,
+       datasets: Registration::STATUS.map do |s|
+         {
+           :label => I18n.t(s, :scope => "registration.status"),
+           :data => dates.map{ |d|
+             @event.registrations.by_status(s).where( "created_at >= ? AND created_at <= ?", d.beginning_of_day, d.end_of_day).count
+           },
+           borderColor: status_colors[s]
+         }
+       end
+     }
+   end

跟上一节类似,data 有有两个资料集分别是报名尚未完成和报名完成。

编辑 app/views/admin/event/show.html.erb 加上图表,这里改成用 line 折线图:

app/views/admin/event/show.html.erb

   <hr>

+  <canvas id="myChart3" width="400" height="200"></canvas>
+  <script>
+  var ctx3 = document.getElementById("myChart3");
+  var myChart3 = new Chart(ctx3, {
+      type: 'line',
+      data: <%= raw @data3.to_json %>,
+      options: {
+          scales: {
+              yAxes: [{
+                  ticks: {
+                      beginAtZero:true
+                  }
+              }]
+          }
+      }
+  });
+  </script>


 

用户权限管理user.role = 'admin'

rails g migration add_role_to_users role: string

rails db:migrate

用 role 的设计好处是可以扩充不同角色,让我们新设计一个 editor 角色:

  • admin 有全部后台权限
  • editor 可以进入后台管理活动,但不能管理用户

在app/controllers/admin_controller.rb

   def require_editor!
     if current_user.role != "editor" && current_user.role != "admin"
       flash[:alert] = "您的权限不足"
       redirect_to root_path
     end
   end

    def require_admin!
      if current_user.role != "admin"
        flash[:alert] = "您的权限不足"
        redirect_to root_path
      end
    end

然后在不同的controller中选择不同的权限审查: before_action :require_xxx!

 

编辑用户角色

除了第一个管理员需要进入 rails console 编辑角色之外,在编辑用户那一页,我们可以实作一个下拉选单来编辑角色。

在user.rb中增加2个角色,使用常量ROLES = ['admin', 'editor']

编辑 app/views/admin/users/edit.html.erb

app/views/admin/users/edit.html.erb

+  <div class="form-group">
+    <%= f.label :role %>
+    <%= f.select :role, User::ROLES.map{ |x| [t(x, :scope => "user.role"), x] }, { :include_blank => true }, :class => "form-control" %>
+  </div>

编辑 config/locales/zh-CN.yml 加上翻译。

编辑 app/controllers/admin/users_controller.rb

app/controllers/admin/users_controller.rb

   def user_params
     params.require(:user).permit(:email, :role, :group_ids => [])
   end

⚠️ :group_ids => [] 是数组集合必须放到最后面。否则会报告❌。

这样就可以编辑用户的角色了。请编辑任一个用户变成 editor,然后登出,改用那个帐号登入(假用户的密码是12345678),就可以测试上一节的权限检查了:可以活动管理,但是点用户管理会跳出权限不足。


 

权限重构

  1.  既然 editor 没有权限可以管理用户,那么在主选单上就不应该出现用户管理的连结。
  2.  因为if的逻辑和在controller中是一样的,所以可以重构:把它们的逻辑提取放到user.rb中。

 

2个权限的gem



 

 

HTML E-mail 寄送

gem  letter_opener

 本机开发的时候,不需要真的寄出 E-mail,安装 letter_opener,这样 Rails 会在寄信时,自动打开浏览器进行预览。

  group :development do
    gem 'letter_opener'

config/environments/development.rb

+  config.action_mailer.delivery_method = :letter_opener
   config.action_mailer.default_url_options = { :host => 'localhost:3000' }

可以使用重置密码进行测试。


 

 

寄送报名完成的 E-mail

目标:完成报名后,寄出通知 E-mail

执行 rails g mailer notification

 

app/mailers/notification_mailer.rb

  class NotificationMailer < ApplicationMailer

+   def confirmed_registration(registration)
+     @registration = registration
+     @event = registration.event
+
+     mail( :to => @registration.email, :subject =>   I18n.t("notification.subject.confirmed_registration", :name => @event.name) )
+   end

  end

config/locales/zh-CN.yml

  "zh-CN":
+   notification:
+     subject:
+       confirmed_registration: "报名成功: %{name}"

 ⚠️  translate(key, options= {} ) 可以传递变量

新增 app/views/notification_mailer/confirmed_registration.text.erb 是 E-mail 样板

Hi <%= @registration.name %>,

您已完成报名 <%= @event.name %> <%= event_url(@event) %>

 

编辑 app/controllers/registrations_controller.rb 在报名完成后寄出 E-mail

app/controllers/registrations_controller.rb

  def step3_update
    略...
    if @registration.update(registration_params)
      flash[:notice] = "报名成功"

+     NotificationMailer.confirmed_registration(@registration).deliver_later

      redirect_to event_registration_path(@event, @registration)
    else
      render "step3"
    end
  end

纯文字的 E-mail 好处是排版简单, 所有的 Email 阅读器都可以顺利打开,不过缺点就是因为没有 HTML,所以不能放超连结,只能直接放网址 (不过大部分的 E-mail 阅读器都能判断这是网址,便会自动帮你加上超连结)、不能放直接放图片(img)、不能放表格(table)等等。 


 

 

可以使用HTML样板代替 text.erb

app/views/notification_mailer/confirmed_registration.html.erb

-   Hi <%= @registration.name %>,
+  <p>Hi <%= @registration.name %>,</p>

-  您已完成报名 <%= @event.name %> <%= event_url(@event) %>
+  <p>您已完成报名 <%= link_to @event.name, event_url(@event) %></p>

提示:

   由用户触发的系统信比较适合用 HTML E-mail,用户会感觉那是系统自动寄出的信件。但如果是客服回信或意见询问,用纯文字 E-mail 可能反而比较有诚意喔。因为用户会感觉那是人写的信件,而不是系统自动寄出的信。

 

既然用了 HTML E-mail,接下来会想加一些 CSS 样式上去。不过这就是麻烦的地方,E-mail 阅读软件比起浏览器更多样更古老

不能用 <link> 标籤去加载 CSS 档案、有些 E-mail 阅读器不能用 <style> 标籤写 CSS 样式,当然也不可能写 JavaScript。

保险的作法是需要将 CSS 用 Inline (内联)的方式到 HTML 里面

好在 Rails 有个 premailer-rails gem 可以帮我们自动做好这个转换,就可以用本来的写法,它会自动转换成 inline CSS 👍👍👍

 

 

因为 E-mail 的 CSS 属性支援程度不一,如果要完全自己写 CSS 测试会很辛苦,好在我们有现成的 Email Responsive Template 样板可以套。


 

posted @ 2018-08-11 18:50  Mr-chen  阅读(338)  评论(0编辑  收藏  举报