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
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),就可以测试上一节的权限检查了:可以活动管理,但是点用户管理会跳出权限不足。
权限重构
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 样板可以套。