tallty_import_export 1.0.35 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +20 -16
- data/lib/tallty_import_export/attr/export_header.rb +100 -0
- data/lib/tallty_import_export/attr.rb +1 -0
- data/lib/tallty_import_export/export.rb +73 -25
- data/lib/tallty_import_export/export_form.rb +134 -0
- data/lib/tallty_import_export/export_payload/cell.rb +101 -0
- data/lib/tallty_import_export/export_payload/cell_column.rb +54 -0
- data/lib/tallty_import_export/export_payload/value.rb +15 -0
- data/lib/tallty_import_export/export_payload.rb +62 -0
- data/lib/tallty_import_export/exportable_form.rb +21 -0
- data/lib/tallty_import_export/import.rb +59 -16
- data/lib/tallty_import_export/importable.rb +4 -0
- data/lib/tallty_import_export/version.rb +1 -1
- data/lib/tallty_import_export.rb +8 -1
- data/tallty_import_export.gemspec +2 -2
- metadata +24 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72c586f1a3e13625520d1ce6378e478ee8dc6cf0055a16528f63ead86c20794f
|
4
|
+
data.tar.gz: 857a44c2a36c69347a2baed91855d943a16581427bdd1b562dd02e54d4a7d97c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ae472d4119ebb57fe5066615d49001ba99a417a067d0586f25010338e7bba1d5fdda737cf8a7892483e68b079af065e21862e87225409f3a7f2e7e0683f8d5f
|
7
|
+
data.tar.gz: fd6af86be0af8b552d2d5170e86b807a301550e7b7f3e604fa0cef539fe6f4630083a4117a494071bc734f83fb29950425f6cdb147b82e130b978c0a2fc8af6f
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tallty_import_export (1.0.
|
4
|
+
tallty_import_export (1.0.35)
|
5
5
|
activesupport
|
6
|
+
attr_json
|
6
7
|
caxlsx
|
7
8
|
redis
|
8
9
|
redis-objects
|
@@ -14,36 +15,40 @@ PATH
|
|
14
15
|
GEM
|
15
16
|
remote: https://gems.ruby-china.com/
|
16
17
|
specs:
|
17
|
-
activemodel (
|
18
|
-
activesupport (=
|
19
|
-
|
18
|
+
activemodel (7.0.2.4)
|
19
|
+
activesupport (= 7.0.2.4)
|
20
|
+
activerecord (7.0.2.4)
|
21
|
+
activemodel (= 7.0.2.4)
|
22
|
+
activesupport (= 7.0.2.4)
|
23
|
+
activesupport (7.0.2.4)
|
20
24
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
21
25
|
i18n (>= 1.6, < 2)
|
22
26
|
minitest (>= 5.1)
|
23
27
|
tzinfo (~> 2.0)
|
24
|
-
|
25
|
-
|
28
|
+
attr_json (1.4.0)
|
29
|
+
activerecord (>= 5.0.0, < 7.1)
|
30
|
+
caxlsx (3.2.0)
|
26
31
|
htmlentities (~> 4.3, >= 4.3.4)
|
27
32
|
marcel (~> 1.0)
|
28
33
|
nokogiri (~> 1.10, >= 1.10.4)
|
29
34
|
rubyzip (>= 1.3.0, < 3)
|
30
|
-
concurrent-ruby (1.1.
|
35
|
+
concurrent-ruby (1.1.10)
|
31
36
|
diff-lcs (1.4.4)
|
32
37
|
htmlentities (4.3.4)
|
33
|
-
i18n (1.
|
38
|
+
i18n (1.10.0)
|
34
39
|
concurrent-ruby (~> 1.0)
|
35
40
|
marcel (1.0.2)
|
36
|
-
mini_portile2 (2.
|
37
|
-
minitest (5.
|
38
|
-
nokogiri (1.
|
39
|
-
mini_portile2 (~> 2.
|
41
|
+
mini_portile2 (2.8.0)
|
42
|
+
minitest (5.15.0)
|
43
|
+
nokogiri (1.13.4)
|
44
|
+
mini_portile2 (~> 2.8.0)
|
40
45
|
racc (~> 1.4)
|
41
|
-
racc (1.
|
46
|
+
racc (1.6.0)
|
42
47
|
rake (12.3.3)
|
43
|
-
redis (4.
|
48
|
+
redis (4.6.0)
|
44
49
|
redis-objects (1.5.1)
|
45
50
|
redis (~> 4.2)
|
46
|
-
roo (2.
|
51
|
+
roo (2.9.0)
|
47
52
|
nokogiri (~> 1)
|
48
53
|
rubyzip (>= 1.3.0, < 3.0.0)
|
49
54
|
roo-xls (1.2.0)
|
@@ -74,7 +79,6 @@ GEM
|
|
74
79
|
tallty_duck_record
|
75
80
|
tzinfo (2.0.4)
|
76
81
|
concurrent-ruby (~> 1.0)
|
77
|
-
zeitwerk (2.4.2)
|
78
82
|
zip-zip (0.3)
|
79
83
|
rubyzip (>= 1.0.0)
|
80
84
|
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module TalltyImportExport
|
2
|
+
module Attr
|
3
|
+
class ExportHeaderItem
|
4
|
+
include AttrJson::Model
|
5
|
+
attr_json_config(bad_cast: :as_nil, unknown_key: :allow)
|
6
|
+
|
7
|
+
attr_json :key, :string
|
8
|
+
attr_json :name, :string
|
9
|
+
attr_json :attr_type, :string
|
10
|
+
attr_json :format, :string
|
11
|
+
attr_json :method, :string
|
12
|
+
attr_json :chain, :string, array: true
|
13
|
+
attr_json :merge, :boolean
|
14
|
+
attr_json :json, :string
|
15
|
+
attr_json :select, ActiveModel::Type::Value.new, array: true
|
16
|
+
attr_json :source, :boolean
|
17
|
+
attr_json :proc, ActiveModel::Type::Value.new, array: true
|
18
|
+
attr_json :children, self.to_type, array: true
|
19
|
+
|
20
|
+
attr_accessor :depth, :parent_path, :seq
|
21
|
+
|
22
|
+
def calcute_flatten_value _depth, _parent_path
|
23
|
+
@depth = _depth
|
24
|
+
@parent_path = _parent_path
|
25
|
+
if children && children.count > 0
|
26
|
+
children.map do |child|
|
27
|
+
child.calcute_flatten_value(_depth + 1, [*_parent_path, self])
|
28
|
+
end.compact.flatten
|
29
|
+
else
|
30
|
+
[self]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def cluster_seqs
|
35
|
+
calcute_flatten_value(0, []).map(&:seq)
|
36
|
+
end
|
37
|
+
|
38
|
+
def seq
|
39
|
+
@seq ||= SecureRandom.uuid
|
40
|
+
end
|
41
|
+
|
42
|
+
# def living_alone?
|
43
|
+
# depth == 0 && children.present?
|
44
|
+
# end
|
45
|
+
end
|
46
|
+
|
47
|
+
class ExportHeader
|
48
|
+
include AttrJson::Model
|
49
|
+
attr_json_config(bad_cast: :as_nil, unknown_key: :allow)
|
50
|
+
|
51
|
+
attr_json :items, ExportHeaderItem.to_type, array: true
|
52
|
+
|
53
|
+
attr_reader :header_seq_to_axios
|
54
|
+
|
55
|
+
def flatten_value
|
56
|
+
items.map do |item|
|
57
|
+
item.calcute_flatten_value(0, [])
|
58
|
+
end.flatten
|
59
|
+
end
|
60
|
+
|
61
|
+
def height
|
62
|
+
flatten_value.map(&:depth).max + 1
|
63
|
+
end
|
64
|
+
|
65
|
+
def header_lines
|
66
|
+
result = []
|
67
|
+
|
68
|
+
height.times.each do |row_index|
|
69
|
+
col_index = 0
|
70
|
+
|
71
|
+
next_line = flatten_value.map do |header|
|
72
|
+
next_header = [*header.try(:parent_path), header]&.[](row_index)
|
73
|
+
|
74
|
+
@header_seq_to_axios ||= {}
|
75
|
+
|
76
|
+
|
77
|
+
if next_header
|
78
|
+
@header_seq_to_axios[next_header.seq] ||= []
|
79
|
+
@header_seq_to_axios[next_header.seq].push([col_index, row_index])
|
80
|
+
end
|
81
|
+
|
82
|
+
# 处理最后一列
|
83
|
+
if (row_index === height - 1)
|
84
|
+
@header_seq_to_axios[header.seq] ||= []
|
85
|
+
@header_seq_to_axios[header.seq].push([col_index, row_index])
|
86
|
+
end
|
87
|
+
|
88
|
+
col_index += 1
|
89
|
+
|
90
|
+
# 下一级继续递归 或 保持自己以合并
|
91
|
+
next_header || header
|
92
|
+
end
|
93
|
+
|
94
|
+
result.push(next_line)
|
95
|
+
end
|
96
|
+
result
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'tallty_import_export/attr/export_header'
|
@@ -1,9 +1,10 @@
|
|
1
1
|
module TalltyImportExport
|
2
2
|
class Export
|
3
|
-
attr_reader :klass
|
3
|
+
attr_reader :klass, :context
|
4
4
|
|
5
5
|
def initialize klass
|
6
6
|
@klass = klass
|
7
|
+
@context = Context.new({})
|
7
8
|
end
|
8
9
|
|
9
10
|
# [
|
@@ -16,6 +17,7 @@ module TalltyImportExport
|
|
16
17
|
# { key: 'user_department_name', name: '考核人部门' },
|
17
18
|
# { key: 'state', name: '考核状态', method: :state_zh },
|
18
19
|
# { key: 'score', name: '考核分', source: true },
|
20
|
+
# { key: 'model_key', name: '关键字', list: [ { key: 'model_key', name: '新属性' } ]},
|
19
21
|
# ]
|
20
22
|
# export_headers_result / headers
|
21
23
|
# key: 属性的英文名,可以支持user.name这样的方式
|
@@ -30,6 +32,7 @@ module TalltyImportExport
|
|
30
32
|
# select: [{ label: '已报备', value: 'submitted'}, ...],需要转换的枚举类型
|
31
33
|
# source: true,如果source为true,代表从association_record 进行属性查询
|
32
34
|
# proc: proc或者lamda,支持call,传入 record 和 context
|
35
|
+
# list: 对于list布局的,进行嵌套,生成子表格
|
33
36
|
|
34
37
|
def export_xlsx records, **options
|
35
38
|
records = with_scope records
|
@@ -38,6 +41,7 @@ module TalltyImportExport
|
|
38
41
|
options = options.with_indifferent_access
|
39
42
|
|
40
43
|
Axlsx::Package.new do |pack|
|
44
|
+
pack.use_shared_strings = true
|
41
45
|
workbook = pack.workbook
|
42
46
|
|
43
47
|
if @group_by.present?
|
@@ -45,20 +49,21 @@ module TalltyImportExport
|
|
45
49
|
records.group_by { |record| record.send(@group_by)}.each do |key, group_records|
|
46
50
|
next unless key.present?
|
47
51
|
@group_key = key
|
48
|
-
export_workbook workbook, group_records
|
52
|
+
export_workbook workbook, group_records, **options
|
49
53
|
end
|
50
54
|
else
|
51
55
|
records.group(@group_by).count.keys.each do |key|
|
52
56
|
next unless key.present?
|
53
57
|
@group_key = key
|
54
|
-
export_workbook workbook, records.ransack("#{@group_where}" => key).result
|
58
|
+
export_workbook workbook, records.ransack("#{@group_where}" => key).result, **options
|
55
59
|
end
|
56
60
|
end
|
57
61
|
else
|
58
|
-
export_workbook workbook, records
|
62
|
+
export_workbook workbook, records, **options
|
59
63
|
end
|
60
64
|
|
61
65
|
file_path = File.join(Rails.root, 'public', 'export')
|
66
|
+
# file_path = File.join('/Users/mushu/', 'export')
|
62
67
|
FileUtils.mkdir_p(file_path) unless Dir.exist?(file_path)
|
63
68
|
file_name = "#{Time.now.strftime('%Y%m%d%H%M%S')}#{@filename}.xlsx"
|
64
69
|
file_path_with_name = File.join(file_path, file_name)
|
@@ -67,14 +72,14 @@ module TalltyImportExport
|
|
67
72
|
end
|
68
73
|
end
|
69
74
|
|
70
|
-
def export_workbook workbook, association_records
|
75
|
+
def export_workbook workbook, association_records, **options
|
71
76
|
# excel导出样式
|
72
77
|
alignment = { vertical: :center, horizontal: :center }
|
73
78
|
border = { color: '969696', style: :thin }
|
74
79
|
title1 = workbook.styles.add_style(alignment: alignment, border: border, sz: 12, b: true)
|
75
80
|
title2 = workbook.styles.add_style(alignment: alignment, border: border, bg_color: "2a5caa", sz: 12, fg_color: "fffffb")
|
76
81
|
title3 = workbook.styles.add_style(alignment: alignment.merge(wrap_text: true), border: border, sz: 10)
|
77
|
-
headers = export_headers_result
|
82
|
+
headers = export_headers_result **options
|
78
83
|
|
79
84
|
_sheet_name = respond_to?(:sheet_name) ? self.sheet_name : nil
|
80
85
|
|
@@ -93,14 +98,15 @@ module TalltyImportExport
|
|
93
98
|
|
94
99
|
index = 0
|
95
100
|
association_records.each do |association_record|
|
96
|
-
row = []
|
97
101
|
records = @each_method.present? ?
|
98
102
|
(try_method(association_record, @each_method) || [nil]) :
|
99
103
|
[association_record]
|
100
104
|
|
101
105
|
records.each do |record|
|
106
|
+
row = []
|
107
|
+
formats = []
|
108
|
+
index += 1
|
102
109
|
headers.each_with_index do |header, col_index|
|
103
|
-
index += 1
|
104
110
|
_data = header[:source] ?
|
105
111
|
handle_data(association_record, header, index) :
|
106
112
|
handle_data(record, header, index)
|
@@ -116,8 +122,9 @@ module TalltyImportExport
|
|
116
122
|
end
|
117
123
|
end
|
118
124
|
row.push(_data)
|
125
|
+
formats.push(header[:format]&.to_sym || (_data.is_a?(String) ? :string : nil))
|
119
126
|
end
|
120
|
-
sheet.add_row row, style: title3, height: @row_height, types:
|
127
|
+
sheet.add_row row, style: title3, height: @row_height, types: formats
|
121
128
|
last_row = row
|
122
129
|
end
|
123
130
|
end
|
@@ -141,7 +148,7 @@ module TalltyImportExport
|
|
141
148
|
end
|
142
149
|
|
143
150
|
def process_options options = {}
|
144
|
-
options = export_options.merge(options
|
151
|
+
options = export_options.merge(options).with_indifferent_access
|
145
152
|
|
146
153
|
@row_height ||= options.delete(:row_height) || 25
|
147
154
|
@width ||= (options.delete(:width) || 20).to_f
|
@@ -150,14 +157,26 @@ module TalltyImportExport
|
|
150
157
|
@group_where = "#{@group_by}_eq" if @group_by.present?
|
151
158
|
@headers ||= options.delete(:headers)
|
152
159
|
@each_method ||= options.delete(:each_method)
|
160
|
+
@params = options
|
161
|
+
context.params = @params
|
153
162
|
end
|
154
163
|
|
155
164
|
def with_scope records
|
156
165
|
records
|
157
166
|
end
|
158
167
|
|
159
|
-
def export_headers_result
|
160
|
-
@headers
|
168
|
+
def export_headers_result **options
|
169
|
+
if @headers.present? && @group_key.blank?
|
170
|
+
headers_hash = @headers.to_h { |header| [header.with_indifferent_access[:key], header] }.with_indifferent_access
|
171
|
+
export_headers(**options.symbolize_keys).select do |_header|
|
172
|
+
_header.with_indifferent_access[:key].to_s.in?(headers_hash.keys)
|
173
|
+
end.map do |_header|
|
174
|
+
_header = _header.with_indifferent_access
|
175
|
+
_header.merge(headers_hash[_header[:key]].delete_if { |k, v| v.blank? })
|
176
|
+
end
|
177
|
+
else
|
178
|
+
@headers = export_headers(**options.symbolize_keys)
|
179
|
+
end
|
161
180
|
end
|
162
181
|
|
163
182
|
def export_headers **args
|
@@ -165,38 +184,44 @@ module TalltyImportExport
|
|
165
184
|
end
|
166
185
|
|
167
186
|
# 处理一个记录的数据
|
168
|
-
def handle_data record, header, index=0
|
187
|
+
def handle_data record, header, index=0, **opts
|
169
188
|
data =
|
170
189
|
if header[:key] == '_index'
|
171
|
-
index
|
190
|
+
index
|
172
191
|
elsif header[:method].present?
|
173
192
|
send(header[:method], record, header)
|
174
193
|
elsif header[:chain].present?
|
175
194
|
try_chain(record, header[:chain])
|
176
|
-
elsif header[:json]
|
177
|
-
record.send(header[:json])[header[:key]]
|
178
195
|
elsif header[:proc] && header[:proc].respond_to?(:call)
|
179
|
-
header[:proc].call(record)
|
196
|
+
header[:proc].call(record, context)
|
180
197
|
else
|
181
|
-
try_method(record, header[:key])
|
198
|
+
try_method(record, header[:key], prefix: [header[:prefix], header[:json]].compact.join('.'))
|
182
199
|
end
|
183
|
-
data = handle_format(data, header)
|
184
|
-
data =
|
200
|
+
data = handle_format(data, header, **opts)
|
201
|
+
data = handle_data_type(data, **opts)
|
202
|
+
data = handle_select(data, header, **opts)
|
185
203
|
rescue
|
186
204
|
''
|
187
205
|
end
|
188
206
|
|
189
207
|
def try_chain record, arr
|
190
|
-
arr.reduce(record)
|
208
|
+
arr.reduce(record) do |r, m|
|
209
|
+
if r.is_a?(Array)
|
210
|
+
r.try(m) || r.try(:[], m.to_i)
|
211
|
+
else
|
212
|
+
r.try(:[], m) || r.try(:[], m.to_sym) || r.try(m)
|
213
|
+
end
|
214
|
+
end
|
191
215
|
end
|
192
216
|
|
193
|
-
def try_method record, method
|
217
|
+
def try_method record, method, prefix: nil
|
218
|
+
prefix_arr = prefix.to_s.split(/\./)
|
194
219
|
arr = method.to_s.split(/\./)
|
195
|
-
try_chain record, arr
|
220
|
+
try_chain record, prefix_arr + arr
|
196
221
|
end
|
197
222
|
|
198
223
|
# 根据数据类型 attr_type 进行数据的格式化
|
199
|
-
def handle_format data, header
|
224
|
+
def handle_format data, header, **opts
|
200
225
|
case header[:attr_type].to_s
|
201
226
|
when 'string'
|
202
227
|
data.to_s
|
@@ -209,7 +234,30 @@ module TalltyImportExport
|
|
209
234
|
end
|
210
235
|
end
|
211
236
|
|
212
|
-
def
|
237
|
+
def handle_data_type data, **opts
|
238
|
+
if data.is_a?(Time)
|
239
|
+
data.in_time_zone.strftime('%F %H:%M')
|
240
|
+
elsif data.is_a?(Date)
|
241
|
+
data.in_time_zone.strftime('%F')
|
242
|
+
elsif data.is_a?(TrueClass)
|
243
|
+
'是'
|
244
|
+
elsif data.is_a?(FalseClass)
|
245
|
+
'否'
|
246
|
+
elsif data.is_a?(Array) && !opts[:keep_array]
|
247
|
+
if data.first.is_a?(Hash) && data.first&.dig('url').present?
|
248
|
+
# 文件array
|
249
|
+
data.map do |item|
|
250
|
+
item.dig('url')
|
251
|
+
end.compact.join("\n")
|
252
|
+
else
|
253
|
+
data.map(&:to_s).compact.join("\n")
|
254
|
+
end
|
255
|
+
else
|
256
|
+
data
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def handle_select data, header, **opts
|
213
261
|
if header[:select].present?
|
214
262
|
select_option = header[:select].find { |option| option[:value].to_s == data.to_s }
|
215
263
|
select_option.present? ? select_option[:label] : data
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module TalltyImportExport
|
2
|
+
class ExportForm < TalltyImportExport::Export
|
3
|
+
def initialize *attrs
|
4
|
+
super(*attrs)
|
5
|
+
end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def export_template_xlsx form
|
9
|
+
export_xlsx([], {
|
10
|
+
headers: form_transfer_to_headers(form),
|
11
|
+
header_only: true
|
12
|
+
})
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def export_workbook workbook, association_records, **options
|
17
|
+
# excel导出样式
|
18
|
+
alignment = { vertical: :center, horizontal: :center }
|
19
|
+
border = { color: '969696', style: :thin }
|
20
|
+
title1 = workbook.styles.add_style(alignment: alignment, border: border, sz: 12, b: true)
|
21
|
+
title2 = workbook.styles.add_style(alignment: alignment, border: border, bg_color: "2a5caa", sz: 12, fg_color: "fffffb")
|
22
|
+
title3 = workbook.styles.add_style(alignment: alignment.merge(wrap_text: true), border: border, sz: 10)
|
23
|
+
_sheet_name = (respond_to?(:sheet_name) ? self.sheet_name : nil) || options[:sheet_name]
|
24
|
+
|
25
|
+
header_obj = export_headers_result **options
|
26
|
+
|
27
|
+
workbook.add_worksheet(name: _sheet_name) do |sheet|
|
28
|
+
index = 0
|
29
|
+
|
30
|
+
if respond_to?(:first_header)
|
31
|
+
row_index = Axlsx.col_ref(headers.size - 1)
|
32
|
+
sheet.merge_cells("A1:#{row_index}1")
|
33
|
+
sheet.add_row [first_header], style: title1, height: 30
|
34
|
+
index += 1
|
35
|
+
end
|
36
|
+
|
37
|
+
header_obj.header_lines.each do |header_line|
|
38
|
+
sheet.add_row(header_line.map(&:name), style: title2, height: 25)
|
39
|
+
index += 1
|
40
|
+
end
|
41
|
+
|
42
|
+
# 合并相同 header
|
43
|
+
header_obj.header_seq_to_axios.values.each do |axios_ary|
|
44
|
+
if axios_ary.count > 1
|
45
|
+
top_right = [axios_ary.map(&:first).min, axios_ary.map(&:last).min]
|
46
|
+
bottom_left = [axios_ary.map(&:first).max, axios_ary.map(&:last).max]
|
47
|
+
sheet.merge_cells(
|
48
|
+
Axlsx::cell_r(top_right.first, top_right.last) + ':' + Axlsx::cell_r(bottom_left.first, bottom_left.last)
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
return if options[:header_only]
|
54
|
+
|
55
|
+
value_seq_to_axios = {}
|
56
|
+
|
57
|
+
formats = []
|
58
|
+
association_records.each do |association_record|
|
59
|
+
records = @each_method.present? ?
|
60
|
+
(try_method(association_record, @each_method) || [nil]) :
|
61
|
+
[association_record]
|
62
|
+
|
63
|
+
records.each do |record|
|
64
|
+
value_living_alone_col_index_to_value_count = {}
|
65
|
+
|
66
|
+
payload = TalltyImportExport::ExportPayload.new(record, header: header_obj) do |payload, header, **opts|
|
67
|
+
_data = header[:source] ?
|
68
|
+
handle_data(association_record, header, index, **opts) :
|
69
|
+
handle_data(payload, header, index, **opts)
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# p '----------------------------------------------------------------'
|
74
|
+
# payload.lines.each { |x| p x.map { |x| x.try(:value) || '________' } }
|
75
|
+
|
76
|
+
payload.lines.each_with_index do |line, row_index|
|
77
|
+
row = []
|
78
|
+
line.each_with_index do |value, col_index|
|
79
|
+
value_living_alone_col_index_to_value_count[col_index] ||= 0
|
80
|
+
|
81
|
+
if (TalltyImportExport::ExportPayload::Value === value)
|
82
|
+
row << value.value
|
83
|
+
value_living_alone_col_index_to_value_count[col_index] += 1
|
84
|
+
unless value_seq_to_axios[value.seq]
|
85
|
+
value_seq_to_axios[value.seq] = []
|
86
|
+
end
|
87
|
+
value_seq_to_axios[value.seq] << [col_index , row_index + index]
|
88
|
+
else
|
89
|
+
row << nil
|
90
|
+
end
|
91
|
+
formats.push(
|
92
|
+
header_obj.flatten_value[col_index].format&.to_sym || (row.last.is_a?(String) ? :string : nil)
|
93
|
+
)
|
94
|
+
end
|
95
|
+
sheet.add_row(row, style: title3, height: @row_height, types: formats)
|
96
|
+
formats = []
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
# 合并仅有一个值的一列中所有格子
|
101
|
+
value_living_alone_col_index_to_value_count.each_pair do |col_index, count|
|
102
|
+
if count == 1
|
103
|
+
sheet.merge_cells(
|
104
|
+
Axlsx::cell_r(col_index, index) + ':' + Axlsx::cell_r(col_index, index + payload.max_matrix_height - 1)
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
index += payload.max_matrix_height
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# 合并相同值
|
114
|
+
value_seq_to_axios.values.each do |axios_ary|
|
115
|
+
if axios_ary.count > 1
|
116
|
+
top_right = [axios_ary.map(&:first).min, axios_ary.map(&:last).min]
|
117
|
+
bottom_left = [axios_ary.map(&:first).max, axios_ary.map(&:last).max]
|
118
|
+
sheet.merge_cells(
|
119
|
+
Axlsx::cell_r(top_right.first, top_right.last) + ':' + Axlsx::cell_r(bottom_left.first, bottom_left.last)
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
def export_headers_result **options
|
128
|
+
@headers = options.symbolize_keys[:headers]
|
129
|
+
super(**options)
|
130
|
+
@headers = TalltyImportExport::Attr::ExportHeader.new({ items: @headers })
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
class TalltyImportExport::ExportPayload::Cell
|
2
|
+
attr_accessor :cell_cluster, :value, :header, :parent_path
|
3
|
+
|
4
|
+
# cell_cluster 是表格纵向 集合
|
5
|
+
def initialize header, value, context, parent_path=[], &value_handler
|
6
|
+
@value = value
|
7
|
+
@header = header
|
8
|
+
@value_handler = value_handler
|
9
|
+
|
10
|
+
# context
|
11
|
+
@context = context
|
12
|
+
|
13
|
+
value.seq_chain.each do |s|
|
14
|
+
# s = seq
|
15
|
+
@context[:header_seq_to_value_seq_to_value][header.seq] ||= {}
|
16
|
+
@context[:header_seq_to_value_seq_to_value][header.seq][s] = value # NOTE: value 对象
|
17
|
+
end
|
18
|
+
|
19
|
+
# parent
|
20
|
+
@parent_path = parent_path
|
21
|
+
|
22
|
+
@context[:parent_cell_seq_to_children_cell_value_array][parent_cell_seq] ||= []
|
23
|
+
@context[:parent_cell_seq_to_children_cell_value_array][parent_cell_seq].push(@value)
|
24
|
+
|
25
|
+
@cell_cluster = []
|
26
|
+
end
|
27
|
+
|
28
|
+
def seq
|
29
|
+
@seq ||= SecureRandom.uuid
|
30
|
+
end
|
31
|
+
|
32
|
+
def parent_cell_seq
|
33
|
+
@parent_path.last&.seq
|
34
|
+
end
|
35
|
+
|
36
|
+
def depth
|
37
|
+
@parent_path.count
|
38
|
+
end
|
39
|
+
|
40
|
+
def header_chain_seqs
|
41
|
+
[*@parent_path, self].map { |cell| cell.header.cluster_seqs }.reduce(:concat)
|
42
|
+
end
|
43
|
+
|
44
|
+
def divise
|
45
|
+
if @header.children.present? && @value.value
|
46
|
+
new_cells = @header.children.map do |child_header|
|
47
|
+
val = @value_handler.call(@value.value, child_header.as_json.symbolize_keys, keep_array: true)
|
48
|
+
if (child_header.children.present? && val.is_a?(Array))
|
49
|
+
val.map do |val|
|
50
|
+
cell = TalltyImportExport::ExportPayload::Cell.new(
|
51
|
+
child_header,
|
52
|
+
TalltyImportExport::ExportPayload::Value.new(val, @value.chain, inherited: true),
|
53
|
+
@context,
|
54
|
+
[*self.parent_path, self],
|
55
|
+
&@value_handler
|
56
|
+
)
|
57
|
+
cell
|
58
|
+
end
|
59
|
+
else
|
60
|
+
val = @value_handler.call(@value.value, child_header.as_json.symbolize_keys)
|
61
|
+
TalltyImportExport::ExportPayload::Cell.new(
|
62
|
+
child_header,
|
63
|
+
TalltyImportExport::ExportPayload::Value.new(val, @value.chain),
|
64
|
+
@context,
|
65
|
+
[*self.parent_path, self],
|
66
|
+
&@value_handler
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end.flatten
|
70
|
+
new_cells.each(&:divise)
|
71
|
+
@cell_cluster.concat(new_cells.map(&:cell_cluster))
|
72
|
+
else
|
73
|
+
@cell_cluster = [self]
|
74
|
+
end
|
75
|
+
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
def grow_to_line
|
80
|
+
@context[:flatten_headers].
|
81
|
+
select do |header|
|
82
|
+
header_chain_seqs.include?(header.seq)
|
83
|
+
end.
|
84
|
+
map do |header|
|
85
|
+
target_value = nil
|
86
|
+
|
87
|
+
# 兄弟细胞上先查找,以解决同级兄弟细胞父一样,而导致的值覆盖
|
88
|
+
brother_cell_value_seqs = (@context[:parent_cell_seq_to_children_cell_value_array][parent_cell_seq] || []).map(&:seq)
|
89
|
+
|
90
|
+
[*brother_cell_value_seqs, *@value.seq_chain.uniq.reverse].each do |seq|
|
91
|
+
next if target_value
|
92
|
+
# seq = @value.seq
|
93
|
+
if @context[:header_seq_to_value_seq_to_value][header.seq]&.[](seq)
|
94
|
+
target_value = @context[:header_seq_to_value_seq_to_value][header.seq][seq]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
target_value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# 一个 最大的竖列,分子列前的竖列
|
2
|
+
class TalltyImportExport::ExportPayload::CellColumn
|
3
|
+
attr_reader :header, :payload, :context, :cell_clusters, :flatten_cells, :tallest_cell_cluster
|
4
|
+
|
5
|
+
def initialize header, value, context, &value_handler
|
6
|
+
@header = header
|
7
|
+
@value = value
|
8
|
+
@context = context
|
9
|
+
@cell_clusters = []
|
10
|
+
@value_handler = value_handler
|
11
|
+
|
12
|
+
val = @value_handler.call(@value.value, @header.as_json.symbolize_keys, keep_array: true)
|
13
|
+
if header.children && val.is_a?(Array)
|
14
|
+
@cell_clusters = val.map do |val|
|
15
|
+
TalltyImportExport::ExportPayload::Cell.new(
|
16
|
+
@header,
|
17
|
+
TalltyImportExport::ExportPayload::Value.new(val, @value.chain),
|
18
|
+
@context,
|
19
|
+
&@value_handler
|
20
|
+
)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
@cell_clusters = [
|
24
|
+
TalltyImportExport::ExportPayload::Cell.new(
|
25
|
+
@header,
|
26
|
+
TalltyImportExport::ExportPayload::Value.new(
|
27
|
+
@value_handler.call(@value.value, @header.as_json.symbolize_keys),
|
28
|
+
@value.chain,
|
29
|
+
),
|
30
|
+
@context,
|
31
|
+
&@value_handler
|
32
|
+
)
|
33
|
+
]
|
34
|
+
end
|
35
|
+
|
36
|
+
@flatten_cells = []
|
37
|
+
|
38
|
+
cell_divise
|
39
|
+
end
|
40
|
+
|
41
|
+
def cell_divise
|
42
|
+
divised_cell_clusters = @cell_clusters.map do |cell_cluster|
|
43
|
+
cell_cluster.divise.cell_cluster
|
44
|
+
end
|
45
|
+
|
46
|
+
@flatten_cells = divised_cell_clusters.flatten
|
47
|
+
|
48
|
+
depth_to_cells_mapping = @flatten_cells.group_by { |cell| [cell.header.seq] }
|
49
|
+
|
50
|
+
@tallest_cell_cluster = depth_to_cells_mapping.values.max { |a, b| a.count <=> b.count } || []
|
51
|
+
|
52
|
+
self
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class TalltyImportExport::ExportPayload::Value
|
2
|
+
attr_reader :value, :chain, :seq_chain
|
3
|
+
|
4
|
+
def initialize(val, chain=[], inherited: false)
|
5
|
+
@value = val
|
6
|
+
@parent_chain = chain
|
7
|
+
|
8
|
+
@chain = inherited ? @parent_chain : [*@parent_chain, self]
|
9
|
+
@seq_chain = @chain.map(&:seq)
|
10
|
+
end
|
11
|
+
|
12
|
+
def seq
|
13
|
+
@seq ||= SecureRandom.uuid
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
|
3
|
+
class TalltyImportExport::ExportPayload
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
|
6
|
+
autoload :Value
|
7
|
+
autoload :CellColumn
|
8
|
+
autoload :Cell
|
9
|
+
|
10
|
+
attr_reader :value, :context, :cell_columns, :max_matrix_height
|
11
|
+
|
12
|
+
def initialize(val, header:, &value_handler)
|
13
|
+
@value = TalltyImportExport::ExportPayload::Value.new(val)
|
14
|
+
@headers = header.items # TalltyImportExport::Attr::ExportHeaderItem array
|
15
|
+
@flatten_headers = header.flatten_value
|
16
|
+
@value_handler = value_handler
|
17
|
+
@context = {
|
18
|
+
header_seq_to_value_seq_to_value: {},
|
19
|
+
parent_cell_seq_to_children_cell_value_array: {},
|
20
|
+
flatten_headers: @flatten_headers,
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def lines
|
25
|
+
@cell_columns = @headers.map do |header|
|
26
|
+
TalltyImportExport::ExportPayload::CellColumn.new(
|
27
|
+
header,
|
28
|
+
self.value,
|
29
|
+
@context,
|
30
|
+
&@value_handler
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
# 分离的,每个初始 header 为一个矩阵
|
35
|
+
matrixes = @cell_columns.map do |cell_column|
|
36
|
+
cell_column.tallest_cell_cluster.map(&:grow_to_line)
|
37
|
+
end
|
38
|
+
|
39
|
+
# 计算所有矩阵中最高高度,用于填充
|
40
|
+
@max_matrix_height = matrixes.map(&:count).max
|
41
|
+
|
42
|
+
# 填充各个矩阵至等高
|
43
|
+
detached_matrixes = matrixes.map do |ragged_col|
|
44
|
+
col = ragged_col.uniq
|
45
|
+
while col.count < @max_matrix_height
|
46
|
+
col.push((col[0].try(:count) || 1).times.to_a)
|
47
|
+
end
|
48
|
+
Matrix[*col]
|
49
|
+
end
|
50
|
+
|
51
|
+
# 合并矩阵
|
52
|
+
result = detached_matrixes.reduce do |out, matrix|
|
53
|
+
Matrix.hstack(out, matrix)
|
54
|
+
end.to_a
|
55
|
+
# .select do |line|
|
56
|
+
# line.select { |value| TalltyImportExport::ExportPayload::Value === value }.count > 0
|
57
|
+
# end
|
58
|
+
|
59
|
+
# result.map { |x| p x.map { |xx| xx.try(:value)}}
|
60
|
+
result
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module TalltyImportExport
|
2
|
+
module ExportableForm
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Common
|
5
|
+
|
6
|
+
included do |base|
|
7
|
+
# base.include(Common)
|
8
|
+
base.const_set('Export', Class.new(TalltyImportExport::ExportForm))
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def export_instance
|
13
|
+
const_get('Export').new(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
def export_xlsx records, **options
|
17
|
+
export_instance.export_xlsx(records, **options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -21,13 +21,35 @@ module TalltyImportExport
|
|
21
21
|
|
22
22
|
# xlsx_file 为 file path or file object or TalltyImportExport::Excel.new
|
23
23
|
def import_xlsx xlsx_file, associations, **options
|
24
|
+
process_xlsx_line_info(xlsx_file, associations, **options) do |line_info, associations|
|
25
|
+
process_line_info(line_info, associations)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def exchange_to_ids xlsx_file, associations, **options
|
30
|
+
errors = []
|
31
|
+
ids = []
|
32
|
+
process_xlsx_line_info(xlsx_file, associations, **options) do |line_info, associations|
|
33
|
+
ids << exchange_line_info_to_id(line_info, associations, errors)
|
34
|
+
end
|
35
|
+
|
36
|
+
if errors.any?
|
37
|
+
error_msg = errors.map do |line_info|
|
38
|
+
"【#{@primary_keys.map { |key| line_info[key] }.join(' - ')}】"
|
39
|
+
end.join(' ')
|
40
|
+
raise RecordNotFountError.new("以下内容未找到: #{error_msg}")
|
41
|
+
end
|
42
|
+
return ids.compact.uniq
|
43
|
+
end
|
44
|
+
|
45
|
+
def process_xlsx_line_info xlsx_file, associations, **options
|
24
46
|
@associations = associations
|
25
47
|
# 先处理获取出来Excel每行的数据, line_info
|
26
48
|
process_options(options)
|
27
49
|
|
28
50
|
if TalltyImportExport::Excel === xlsx_file
|
29
51
|
xlsx_file.rows.each_with_excel_hash(@excel_hash) do |line_info|
|
30
|
-
|
52
|
+
yield line_info.with_indifferent_access, associations
|
31
53
|
end
|
32
54
|
else
|
33
55
|
file_path = xlsx_file.is_a?(String) ? xlsx_file : xlsx_file.path
|
@@ -35,7 +57,7 @@ module TalltyImportExport
|
|
35
57
|
xlsx.each_with_pagename do |_sheetname, sheet|
|
36
58
|
sheet.each(**@excel_hash).with_index do |line_info, index|
|
37
59
|
next if index == 0
|
38
|
-
|
60
|
+
yield line_info.with_indifferent_access, associations
|
39
61
|
end
|
40
62
|
end
|
41
63
|
end
|
@@ -53,7 +75,7 @@ module TalltyImportExport
|
|
53
75
|
end
|
54
76
|
|
55
77
|
def process_options options
|
56
|
-
options = import_options.merge(options
|
78
|
+
options = import_options.merge(options).with_indifferent_access
|
57
79
|
@headers = options.delete(:headers) || import_headers
|
58
80
|
@primary_keys = options.delete(:primary_keys) || @headers.map { |header| header[:primary_key] ? header[:key].to_sym : nil }.compact
|
59
81
|
@skip_keys = options.delete(:skip_keys) || @headers.map { |header| header[:skip] ? header[:key].to_sym : nil }.compact
|
@@ -83,13 +105,13 @@ module TalltyImportExport
|
|
83
105
|
end
|
84
106
|
|
85
107
|
def convert_data line_info
|
86
|
-
line_info.with_indifferent_access
|
87
|
-
|
88
|
-
|
89
|
-
|
108
|
+
info = line_info.with_indifferent_access
|
109
|
+
import_headers_result.reduce({}) do |h, header|
|
110
|
+
k = header[:key]
|
111
|
+
v = info[k]
|
90
112
|
# header[:convert] = handle_xxx
|
91
113
|
# handle_xxx(val, processing_line_info, raw_line_info)
|
92
|
-
val = header[:convert] ? send(header[:convert], v, h,
|
114
|
+
val = header[:convert] ? send(header[:convert], v, h, info) : v
|
93
115
|
if header[:json]
|
94
116
|
h[header[:json]] ||= {}
|
95
117
|
h[header[:json]][k] = val
|
@@ -114,16 +136,15 @@ module TalltyImportExport
|
|
114
136
|
# 需要合并proc,前端没有办法把proc传过来
|
115
137
|
def import_headers_result
|
116
138
|
if @headers.present?
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
header
|
139
|
+
headers_hash = @headers.to_h { |header| [header.with_indifferent_access[:key], header] }.with_indifferent_access
|
140
|
+
import_headers(**context.params.symbolize_keys).select do |_header|
|
141
|
+
_header.with_indifferent_access[:key].to_s.in?(headers_hash.keys)
|
142
|
+
end.map do |_header|
|
143
|
+
_header = _header.with_indifferent_access
|
144
|
+
_header.merge(headers_hash[_header[:key]].delete_if { |k, v| v.blank? })
|
124
145
|
end
|
125
146
|
else
|
126
|
-
@headers = import_headers
|
147
|
+
@headers = import_headers(**options.symbolize_keys)
|
127
148
|
end
|
128
149
|
rescue
|
129
150
|
@headers
|
@@ -174,5 +195,27 @@ module TalltyImportExport
|
|
174
195
|
associations.create!(line_info.clone.except!(*@skip_keys))
|
175
196
|
end
|
176
197
|
end
|
198
|
+
|
199
|
+
def exchange_line_info_to_id line_info, associations, errors
|
200
|
+
# 去除空行内容
|
201
|
+
return unless line_info.values.any?(&:present?)
|
202
|
+
context.line_info = line_info
|
203
|
+
|
204
|
+
return unless primary_keys.present?
|
205
|
+
ids = associations.where(line_info.clone.extract!(*primary_keys)).pluck(:id)
|
206
|
+
|
207
|
+
context.last_line_info = line_info
|
208
|
+
|
209
|
+
errors << line_info unless ids[0]
|
210
|
+
return ids[0]
|
211
|
+
end
|
212
|
+
|
213
|
+
class RecordNotFountError < StandardError
|
214
|
+
attr_accessor :message
|
215
|
+
def initialize message
|
216
|
+
@message = message
|
217
|
+
super()
|
218
|
+
end
|
219
|
+
end
|
177
220
|
end
|
178
221
|
end
|
data/lib/tallty_import_export.rb
CHANGED
@@ -1,20 +1,27 @@
|
|
1
|
+
require 'active_support'
|
1
2
|
require "active_support/core_ext/hash"
|
3
|
+
require "active_support/core_ext/object"
|
2
4
|
require "tallty_import_export/version"
|
3
5
|
require 'axlsx'
|
4
6
|
require 'redis'
|
5
|
-
require 'tallty_form'
|
7
|
+
# require 'tallty_form'
|
8
|
+
require 'attr_json'
|
6
9
|
|
7
10
|
module TalltyImportExport
|
8
11
|
extend ActiveSupport::Autoload
|
9
12
|
|
10
13
|
autoload :Common
|
11
14
|
autoload :Exportable
|
15
|
+
autoload :ExportableForm
|
12
16
|
autoload :Export
|
13
17
|
autoload :Importable
|
14
18
|
autoload :Import
|
15
19
|
autoload :Context
|
16
20
|
autoload :FormConvert
|
17
21
|
autoload :Excel
|
22
|
+
autoload :Attr
|
23
|
+
autoload :ExportPayload
|
24
|
+
autoload :ExportForm
|
18
25
|
|
19
26
|
class Error < StandardError; end
|
20
27
|
|
@@ -14,8 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
|
15
15
|
spec.platform = Gem::Platform::RUBY
|
16
16
|
|
17
|
-
# Specify which files should be added to the gem when it is released.
|
18
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
17
|
+
# Specify which files should be added to the gem when it is released. The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
18
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
20
19
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
20
|
end
|
@@ -31,4 +30,5 @@ Gem::Specification.new do |spec|
|
|
31
30
|
spec.add_dependency('activesupport')
|
32
31
|
spec.add_dependency('redis')
|
33
32
|
spec.add_dependency('redis-objects')
|
33
|
+
spec.add_dependency 'attr_json'
|
34
34
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tallty_import_export
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- liyijie
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zip-zip
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: attr_json
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
125
139
|
description: " import & export for active_record with simple_controller "
|
126
140
|
email:
|
127
141
|
- liyijie825@gmail.com
|
@@ -140,11 +154,19 @@ files:
|
|
140
154
|
- bin/console
|
141
155
|
- bin/setup
|
142
156
|
- lib/tallty_import_export.rb
|
157
|
+
- lib/tallty_import_export/attr.rb
|
158
|
+
- lib/tallty_import_export/attr/export_header.rb
|
143
159
|
- lib/tallty_import_export/common.rb
|
144
160
|
- lib/tallty_import_export/context.rb
|
145
161
|
- lib/tallty_import_export/excel.rb
|
146
162
|
- lib/tallty_import_export/export.rb
|
163
|
+
- lib/tallty_import_export/export_form.rb
|
164
|
+
- lib/tallty_import_export/export_payload.rb
|
165
|
+
- lib/tallty_import_export/export_payload/cell.rb
|
166
|
+
- lib/tallty_import_export/export_payload/cell_column.rb
|
167
|
+
- lib/tallty_import_export/export_payload/value.rb
|
147
168
|
- lib/tallty_import_export/exportable.rb
|
169
|
+
- lib/tallty_import_export/exportable_form.rb
|
148
170
|
- lib/tallty_import_export/form_convert.rb
|
149
171
|
- lib/tallty_import_export/import.rb
|
150
172
|
- lib/tallty_import_export/importable.rb
|