新手问题 再谈重复代码消除 (跟风帖)

small_fish__ · March 19, 2014 · Last by small_fish__ replied at March 19, 2014 · 1961 hits

http://ruby-china.org/topics/17978 帖子后,由于昨天开始在写一个 excel 打印的东西,感觉和楼主遇到了相同的问题。

功能分析:

  1. 针对同一个 model,并提供 4 种 excel 打印模版。
  2. 其中不同模版打印字段不同,但是又有交集,有些字段需要做特殊处理(格式化)。
  3. 打印模版具有相同的头部和尾部。
  4. 4 种模版其实又大致分为 2 类,1 类只有 1 个 header,另外一类又 2 级 header(意思就是打印的 columns 具有 group 的概念,不同的 group 能够有不同的背景色区分。)
  5. 其中有 2 种 excel 能够打印图片。

目前我使用的是 axlsx 这个 gem. 目前没有任何结构的代码

module Printer

   COLUMN_NAMES = {
     :image => _("Image"),
     :brand_name => _("Manufacturer Name"),
     :name => _('Product Name'),
     :dimension => _("Dimensions"),
     :csi_category_code => _('CSI Category'),
     :vendor => _('Vendor Info'),
     :remark => _('notes'),
     :updated_at => _('Last Updated'),
     :symbol => _('Symbol'),
     :description => _('Description'),
     :area => _('Space'),
     :application => _('Application'),
     :manufacturer_website => _('Manufacturer Website'),
     :made_in => _('Made In'),
     :other_brand => _('Acceptable Brands'),
     :lbc_material_title => _("LBC Material Title"),
     :lbc_status => _("LBC Status"),
     :team_member => _("Team Member"),
     :zone => _("Zone"),
     :zone_radius => _("Radius (km)"),
     :sourcing_row_material => _("Sourcing Raw Materials (km)"),
     :red_list_free => _("Red List Free? (y/n)"),
     :product_content => _("Primary Contents"),
     :product_location => _("Product Location"),
     :product_distance => _("Distance from MNFR (km)"),
     :product_responsible_industry => _("Responsible Industry"),
     :product_appiled => _("Exception Applied (y/n)"),
     :manufacturer_location => _("manufacturer Location"),
     :manufacturer_distance => _("Distance from Site (km)"),
     :red_list => _("Red List (y/n)"),
     :app_sourcing => _("App. Sourcing (y/n)"),
     :fsc_certified => _("FSC Certified (y/n)"),
     :lbc_notes => _("LBC Notes"),
     :floor_area => _("Floor Area (m^2)"),
     :height => _("Height (m)"),
     :floor_area => _("Space Volume (m^3)"),
     :iaq_certification => _("IAQ Certification"),
     :conversion_factor => _("Conversion Factor"),
     :test_result => _("Test Result / Limit Value (µg/m^3)"),
     :iaq_notes => _("IAQ Notes"),
     :material_quantity => _("Material Quantity"),
     :formaldehyde => _("Formaldehyde (µg/m^3)"),
     :formaldehyde_projected_emission => _("Formaldehyde Projected Emissions (µg/m^3)"),
     :tvoc => _("TVOC (µg/m^3)"),
     :tvoc_projected_emission => _("TVOC Projected Emissions (µg/m^3)")

   }

   XLSX_STYLES = {
     :title => {
       :bg_color => "ff",
       :fg_color => "00",
       :sz => 14, :b => true,
       :border => { :style => :thin, :color => "00" },
       :alignment => { :horizontal=> :center, :vertical => :center }
     },
     :header => {
       :bg_color => "ff",
       :fg_color => "00",
       :sz => 14,
       :b => true,
       :border => { :style => :thin, :color => "00" },
       :alignment => { :horizontal=> :center, :vertical => :center }
     },
     :center => {
       :alignment => {
         :horizontal=> :center,
         :vertical => :center
       }
     }
   }

   CATEGORT_COLUMNS = {
     :basic => %w(image brand_name name dimension csi_category_code vendor remark updated_at),

     :schedule => %w(symbol description area application image ,

     :lbc => %w(csi_category_code lbc_material_title lbc_status team_member zone),

     :voc => %w(area floor_area height floor_area csi_category_code brand_name )
   }

   def self.print!(records, options={})
     p = Axlsx::Package.new
     p.use_shared_strings = true
     wb = p.workbook
     wb.styles do |s|
       title_style = s.add_style XLSX_STYLES[:title]
       header_style = s.add_style XLSX_STYLES[:header]
       center_cell = s.add_style XLSX_STYLES[:center]
       sheet = wb.add_worksheet

       #render title
       # title = Axlsx::RichText.new
       # title.add_run("#{options[:workspace]}\n", :b => true)
       # title.add_run("#{_('Project:')} #{options[:project]}\n", :b => true)
       # title.add_run("#{_('Date:')} #{Time.now.to_s(:db)}", :b => true)
       # sheet.add_row [title], :height => 40, :style => Array.new( 1, title_style)
       # #sheet.merge_cells("A1:G1")

       #render header
       mode = (options[:mode] || options['mode']).to_sym
       mode = :basic unless CATEGORT_COLUMNS.has_key? mode

       columns = CATEGORT_COLUMNS[mode]
       headers = columns.map{|key| COLUMN_NAMES[key.to_sym]}
       sheet.add_row headers, :height => 30, :style => Array.new( headers.size, header_style)

       #render records content.
       #self.send("print_#{mode}", records, sheet, s)


       records.each_with_index do |material, index|
         values = material.attributes.values_at(*columns)

         #render image
         if columns.index('image') && material.image
           _index = columns.index('image')
           values[_index] = ''
           img = "#{Rails.root}/public#{material.image.file.url.gsub(/\?\d+$/,"")}"
           sheet.add_image(:image_src => img, :noSelect => false, :noMove => false) do |image|
             image.width = 100
             image.height = 100
             image.start_at _index, index+1
           end
         end

         #render vendor info
         if columns.index('vendor') && material.vendor
           _index = columns.index('vendor')
           values[_index] = values[_index].values.join("\n")
         end

         # format updated_at
         if columns.index('updated_at')
           _index = columns.index('updated_at')
           values[_index] = values[_index].localtime.to_s(:db)
         end
         sheet.add_row values, :height => 78, :style => Array.new(columns.size, center_cell)
       end
     end
     p.to_stream.read
   end
 end

目前感觉这代码就不够稳健,无法高度适应 excel 打印样式的自定义,需要重构,我看到上个帖子有兄台提到了模版模式,我也准备采用,带式打印的基本模版,我发现我还没理清。

对代码结构涉及能力不是很强,希望大家能给点意见和建议。。谢谢。

0 Floor has deleted

谢谢。#1 楼 @neverlandxy_naix 一开始我也是用 send 来写的,感觉也不是一个清晰的逻辑。。我感觉问题的核心是能将打印的逻辑清晰化,他人阅读代码不那么费力。

现在纠结是不是需要搞一个 pinter 的继承关系来处理

其实我更想用原生的 xml 来创建了,这样直接通过 view 就搞定了。。只是 xml 创建不知道如何插入图片和表格样式。。

You need to Sign in before reply, if you don't have an account, please Sign up first.