Commit bb4f3054 by Ivan Lan

增加 Import 和 Export

parent 6a22ebe6
module TalltyImportExport
require "ostruct"
class Context < OpenStruct
end
end
module TalltyImportExport
class Export
attr_reader :klass, :headers
def initialize klass
@klass = klass
end
# export_headers / headers
# key: 属性的英文名
# name: 属性的中文名
# attr_type: 属性的类型
# format: excel是否需要特定的格式,目前主要是类似于身份证号,可以用string
# method: 导出时本地调用的方法
# chain: 导出时对象属性通过链式调用
def export_records records, options={}
exportable_defaults(options)
options = options.with_indifferent_access
Axlsx::Package.new do |pack|
workbook = pack.workbook
# excel导出样式
alignment = { vertical: :center, horizontal: :center }
border = { color: '969696', style: :thin }
title1 = workbook.styles.add_style(alignment: alignment, border: border, sz: 16, b: true)
title2 = workbook.styles.add_style(alignment: alignment, border: border, bg_color: "2a5caa", sz: 16, fg_color: "fffffb")
title3 = workbook.styles.add_style(alignment: alignment.merge(wrap_text: true), border: border, sz: 14)
workbook.add_worksheet do |sheet|
if respond_to?(:first_header)
row_index = Axlsx.col_ref(headers.size - 1)
sheet.merge_cells("A1:#{row_index}1")
sheet.add_row [first_header], style: title1, height: 40
end
sheet.add_row @headers.map{|header| header[:name]}, style: title2, height: 39
records.find_each do |record|
row = []
headers.each{ |header| row.push(handle_data(record, header)) }
sheet.add_row row, style: title3, height: @row_height, types: @headers.map{|header| header[:format]}
end
sheet.column_widths *@headers.map{|header| header[:width] || @width}
end
file_path = File.join(Rails.root, 'public', 'export')
FileUtils.mkdir_p(file_path) unless Dir.exist?(file_path)
file_name = "#{Time.now.strftime('%Y%m%d%H%M%S')}#{@filename}.xlsx"
pack.serialize(File.join(file_path, file_name))
url = ActionController::Base.helpers.asset_url("/export/#{file_name}")
return url
end
end
def exportable_defaults options = {}
options = options.with_indifferent_access
@row_height ||= options.delete(:row_height) || 35
@width ||= options.delete(:width) || 30
@filename ||= options.delete(:filename)
self.headers = export_headers
end
def headers= val
@headers = val.map { |header| header.with_indifferent_access }
end
def export_headers
klass.try(:headers) || klass.try(:model_headers)
end
# 处理一个记录的数据
def handle_data record, header
data =
if header[:method].present?
send(header[:method], record, header[:key])
elsif header[:chain].present?
header[:chain].reduce(record) do |obj, method|
obj.send(method)
end
elsif header[:json]
record.send(header[:json])[header[:key]]
else
record.send(header[:key])
end
handle_format(data, header)
rescue
''
end
# 根据数据类型 attr_type 进行数据的格式化
def handle_format data, header
case header[:attr_type].to_s
when 'datetime'
data ? data.strftime('%F %H:%M') : nil
when 'date'
data ? data.strftime('%F') : nil
else
data
end
end
end
end
...@@ -2,101 +2,18 @@ module TalltyImportExport ...@@ -2,101 +2,18 @@ module TalltyImportExport
module Exportable module Exportable
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Common included do |base|
base.include(Common)
# export_headers / headers base.const_set('Export', Class.new(TalltyImportExport::Export))
# key: 属性的英文名
# name: 属性的中文名
# attr_type: 属性的类型
# format: excel是否需要特定的格式,目前主要是类似于身份证号,可以用string
# method: 导出时本地调用的方法
# chain: 导出时对象属性通过链式调用
included do
end end
module ClassMethods module ClassMethods
def export_instance
def export_records records, options={} Export.new(self)
exportable_defaults
options = options.with_indifferent_access
header_keys = options.delete(:keys)
Axlsx::Package.new do |pack|
workbook = pack.workbook
# excel导出样式
alignment = { vertical: :center, horizontal: :center }
border = { color: '969696', style: :thin }
title1 = workbook.styles.add_style(alignment: alignment, border: border, sz: 16, b: true)
title2 = workbook.styles.add_style(alignment: alignment, border: border, bg_color: "2a5caa", sz: 16, fg_color: "fffffb")
title3 = workbook.styles.add_style(alignment: alignment.merge(wrap_text: true), border: border, sz: 14)
workbook.add_worksheet do |sheet|
if respond_to?(:first_header)
row_index = Axlsx.col_ref(headers.size - 1)
sheet.merge_cells("A1:#{row_index}1")
sheet.add_row [first_header], style: title1, height: 40
end
sheet.add_row @headers.map{|header| header[:name]}, style: title2, height: 39
records.find_each do |record|
row = []
@headers.each{ |header| row.push(handle_data(record, header)) }
sheet.add_row row, style: title3, height: @row_height, types: @headers.map{|header| header[:format]}
end
sheet.column_widths *@headers.map{|header| header[:width] || @width}
end
file_path = File.join(Rails.root, 'public', 'export')
FileUtils.mkdir_p(file_path) unless Dir.exist?(file_path)
file_name = "#{Time.now.strftime('%Y%m%d%H%M%S')}#{@filename}.xlsx"
pack.serialize(File.join(file_path, file_name))
url = ActionController::Base.helpers.asset_url("/export/#{file_name}")
return url
end
end end
def exportable_defaults(options = {}) def export_xlsx *args
options = options.with_indifferent_access export_instance.export_xlsx(*args)
@row_height ||= options.delete(:row_height) || 35
@width ||= options.delete(:width) || 30
@filename ||= options.delete(:filename)
@headers = export_headers
@headers.map! {|header| header.with_indifferent_access}
end
def export_headers
self.try(:headers) || self.try(:model_headers)
end
# 处理一个记录的数据
def handle_data record, header
data =
if header[:method].present?
send(header[:method], record, header[:key])
elsif header[:chain].present?
header[:chain].reduce(record) do |obj, method|
obj.send(method)
end
else
record.send(header[:key])
end
handle_format(data, header)
rescue
''
end
# 根据数据类型 attr_type 进行数据的格式化
def handle_format data, header
case header[:attr_type].to_s
when 'datetime'
data ? data.strftime('%F %H:%M') : nil
when 'date'
data ? data.strftime('%F') : nil
else
data
end
end end
end end
end end
......
module TalltyImportExport
class FormConvert
class << self
def import_headers form, column_name: :payload
form.fields.map do |field|
{}.tap do |q|
q[:key] = field.key
q[:name] = field.name
q[:json] = column_name.to_sym
end
end
end
end
end
end
class Import
attr_reader :klass, :content, :headers, :primary_keys
def initialize klass
@klass = klass
@content = Context.new({})
end
# key: 属性的英文名
# name: 属性的中文名
# attr_type: 属性的类型
# format: excel是否需要特定的格式,目前主要是类似于身份证号,可以用string
# convert: 导入时候,把excel的内容转换成导入时候代码逻辑需要的内容
# primary_key: 是否是主键
def import_xlsx xlsx_file, associations, options={}
# 先处理获取出来Excel每行的数据, line_info
options = options.with_indifferent_access
self.headers = options.delete(:headers) || import_headers
self.primary_keys = options.delete(:primary_keys) || headers.map { |header| header[:primary_key] ? header[:key].to_sym : nil }.compact
excel_hash = headers.reduce({}) do |h, header|
h[header[:key]] = header[:name]
h
end
file_path = xlsx_file.is_a?(String) ? xlsx_file : xlsx_file.path
xlsx = Roo::Excelx.new(file_path)
xlsx.each_with_pagename do |_sheetname, sheet|
sheet.each(**excel_hash).with_index do |line_info, index|
next if index == 0
# 转换处理导入的数据格式
line_info = convert_data(line_info)
# 处理每行对于导入的动作,处理line_info
import_record(line_info, associations)
end
end
end
def convert_data line_info
line_info.with_indifferent_access.reduce({}) do |h, (k, v)|
header = headers.find do |_header|
_header[:key].to_sym == k.to_sym
end
val = header[:convert] ? send(header[:convert], v) : v
if header[:json]
h[header[:json]] ||= {}
h[header[:json]][k] = val
else
h[k.to_sym] = val
end
h
end.with_indifferent_access
end
def import_headers
klass.try(:headers) || kless.try(:model_headers)
end
### 这个方法是可以由复杂业务进行重载的 ###
def import_record line_info, associations
if primary_keys.present?
_record = associations.find_or_initialize_by(line_info.clone.extract!(*primary_keys))
_record.update!(line_info.clone.except!(*primary_keys))
else
associations.create!(line_info)
end
end
end
...@@ -2,71 +2,18 @@ module TalltyImportExport ...@@ -2,71 +2,18 @@ module TalltyImportExport
module Importable module Importable
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Common included do |base|
base.include(Common)
# key: 属性的英文名 base.const_set('Import', Class.new(TalltyImportExport::Import))
# name: 属性的中文名
# attr_type: 属性的类型
# format: excel是否需要特定的格式,目前主要是类似于身份证号,可以用string
# convert: 导入时候,把excel的内容转换成导入时候代码逻辑需要的内容
# primary_key: 是否是主键
included do
end end
module ClassMethods module ClassMethods
def import_instance
def import_xlsx xlsx_file, associations, options={} Import.new(self)
# 先处理获取出来Excel每行的数据, line_info
options = options.with_indifferent_access
@headers = options.delete(:headers) || import_headers
@primary_keys = options.delete(:primary_keys) || @headers.map { |header| header[:primary_key] ? header[:key].to_sym : nil }.compact
excel_hash = @headers.reduce({}) do |h, header|
h[header[:key]] = header[:name]
h
end
file_path = xlsx_file.is_a?(String) ? xlsx_file : xlsx_file.path
xlsx = Roo::Excelx.new(file_path)
xlsx.each_with_pagename do |_sheetname, sheet|
sheet.each(**excel_hash).with_index do |line_info, index|
next if index == 0
# 转换处理导入的数据格式
line_info = convert_data line_info, @headers
# 处理每行对于导入的动作,处理line_info
import_record line_info, associations, primary_keys: @primary_keys
end
end
end
def convert_data line_info, headers
line_info.with_indifferent_access.reduce({}) do |h, (k, v)|
header = headers.find do |_header|
_header[:key].to_sym == k.to_sym
end
h[k.to_sym] = header[:convert] ?
self.send(header[:convert], v) : v
h
end.with_indifferent_access
end
def import_headers
self.try(:headers) || self.try(:model_headers)
end end
### 这个方法是可以由复杂业务进行重载的 ### def import_xlsx *args
def import_record line_info, associations, primary_keys: [] import_instance.import_xlsx(*args)
record = if primary_keys.present?
_record = associations.find_or_initialize_by(line_info.clone.extract!(*primary_keys))
_record.attributes = line_info.clone.except!(*primary_keys)
_record
else
associations.new(line_info)
end
record.save!
end end
end end
end end
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment