Merge pull request #2114 from SergioCrisostomo/improve-csv

Improve export to CSV functionality
This commit is contained in:
Aresn 2017-10-19 22:47:33 -05:00 committed by GitHub
commit cbe5ccfc8a
5 changed files with 449 additions and 63 deletions

View file

@ -197,9 +197,13 @@
<div class="layout-content-main">
<Table stripe :columns="columns1" :data="data1"></Table>
</div>
<hr style="margin: 10px 0;" />
<div class="layout-content-main">
<Table stripe :columns="columns2" :data="data2" ref="csvTable" />
<i-button type="primary" size="large" @click="exportCSV">
<icon type="ios-download-outline"></icon> Export to CSV
</i-button>
</div>
<div class="layout-copy">
2011-2016 &copy; TalkingData
</div>
</Col>
</Row>
@ -330,8 +334,44 @@
age: 26,
address: '深圳市南山区深南大道'
}
]
],
columns2: [
{
title: '姓名',
key: 'name'
},
{
title: '年龄',
key: 'age'
},
{
title: '地址',
key: 'address'
}
],
data2: [
{
name: '王小明',
age: 18,
address: '北京市朝\n阳区芍药居'
},
{
name: '张小刚',
age: 25,
address: '北京市海,淀区西二旗'
},
{
name: '李小红',
age: 30,
address: '上海市浦东\r新区世纪大道'
},
{
name: '周小伟',
age: 26,
address: '深圳市南山区深南大道'
}
]
};
},
computed: {
iconSize () {
@ -347,7 +387,14 @@
this.spanLeft = 5;
this.spanRight = 19;
}
},
exportCSV () {
this.$refs.csvTable.exportCsv({
filename: '原始数据',
separator: ';',
quoted: true
});
}
}
}
};
</script>

View file

@ -714,8 +714,9 @@
let noHeader = false;
if ('noHeader' in params) noHeader = params.noHeader;
const data = Csv(columns, datas, ',', noHeader);
ExportCsv.download(params.filename, data);
const data = Csv(columns, datas, params, noHeader);
if (params.callback) params.callback(data);
else ExportCsv.download(params.filename, data);
}
},
created () {

View file

@ -1,25 +1,39 @@
// https://github.com/Terminux/react-csv-downloader/blob/master/src/lib/csv.js
/*
inspired by https://www.npmjs.com/package/react-csv-downloader
now removed from Github
*/
const newLine = '\r\n';
const appendLine = (content, row, { separator, quoted }) => {
const line = row.map(data => {
if (!quoted) return data;
// quote data
data = typeof data === 'string' ? data.replace(/"/g, '"') : data;
return `"${data}"`;
});
content.push(line.join(separator));
};
export default function csv(columns, datas, separator = ',', noHeader = false) {
const defaults = {
separator: ',',
quoted: false
};
export default function csv(columns, datas, options, noHeader = false) {
options = Object.assign({}, defaults, options);
let columnOrder;
const content = [];
const column = [];
if (columns) {
columnOrder = columns.map(v => {
if (typeof v === 'string') {
return v;
}
if (typeof v === 'string') return v;
if (!noHeader) {
column.push((typeof v.title !== 'undefined') ? v.title : v.key);
column.push(typeof v.title !== 'undefined' ? v.title : v.key);
}
return v.key;
});
if (column.length > 0) {
content.push(column.join(separator));
}
if (column.length > 0) appendLine(content, column, options);
} else {
columnOrder = [];
datas.forEach(v => {
@ -29,26 +43,16 @@ export default function csv(columns, datas, separator = ',', noHeader = false) {
});
if (columnOrder.length > 0) {
columnOrder = columnOrder.filter((value, index, self) => self.indexOf(value) === index);
if (!noHeader) {
content.push(columnOrder.join(separator));
}
if (!noHeader) appendLine(content, columnOrder, options);
}
}
if (Array.isArray(datas)) {
datas.map(v => {
if (Array.isArray(v)) {
return v;
datas.forEach(row => {
if (!Array.isArray(row)) {
row = columnOrder.map(k => (typeof row[k] !== 'undefined' ? row[k] : ''));
}
return columnOrder.map(k => {
if (typeof v[k] !== 'undefined') {
return v[k];
}
return '';
});
}).forEach(v => {
content.push(v.join(separator));
appendLine(content, row, options);
});
}
return content.join(newLine);

View file

@ -0,0 +1,287 @@
export const csvA = {
columns: [
{
title: '名称',
key: 'name',
fixed: 'left',
width: 200
},
{
title: '展示',
key: 'show',
width: 150,
sortable: true
},
{
title: '唤醒',
key: 'weak',
width: 150,
sortable: true
},
{
title: '登录',
key: 'signin',
width: 150,
sortable: true
},
{
title: '点击',
key: 'click',
width: 150,
sortable: true
},
{
title: '激活',
key: 'active',
width: 150,
sortable: true
},
{
title: '7日留存',
key: 'day7',
width: 150,
sortable: true
},
{
title: '30日留存',
key: 'day30',
width: 150,
sortable: true
},
{
title: '次日留存',
key: 'tomorrow',
width: 150,
sortable: true
},
{
title: '日活跃',
key: 'day',
width: 150,
sortable: true
},
{
title: '周活跃',
key: 'week',
width: 150,
sortable: true
},
{
title: '月活跃',
key: 'month',
width: 150,
sortable: true
}
],
data: [
{
name: '推广名称1',
fav: 0,
show: 7302,
weak: 5627,
signin: 1563,
click: 4254,
active: 1438,
day7: 274,
day30: 285,
tomorrow: 1727,
day: 558,
week: 4440,
month: 5610
},
{
name: '推广名称2',
fav: 0,
show: 4720,
weak: 4086,
signin: 3792,
click: 8690,
active: 8470,
day7: 8172,
day30: 5197,
tomorrow: 1684,
day: 2593,
week: 2507,
month: 1537
},
{
name: '推广名称3',
fav: 0,
show: 7181,
weak: 8007,
signin: 8477,
click: 1879,
active: 16,
day7: 2249,
day30: 3450,
tomorrow: 377,
day: 1561,
week: 3219,
month: 1588
},
{
name: '推广名称4',
fav: 0,
show: 9911,
weak: 8976,
signin: 8807,
click: 8050,
active: 7668,
day7: 1547,
day30: 2357,
tomorrow: 7278,
day: 5309,
week: 1655,
month: 9043
},
{
name: '推广名称5',
fav: 0,
show: 934,
weak: 1394,
signin: 6463,
click: 5278,
active: 9256,
day7: 209,
day30: 3563,
tomorrow: 8285,
day: 1230,
week: 4840,
month: 9908
},
{
name: '推广名称6',
fav: 0,
show: 6856,
weak: 1608,
signin: 457,
click: 4949,
active: 2909,
day7: 4525,
day30: 6171,
tomorrow: 1920,
day: 1966,
week: 904,
month: 6851
},
{
name: '推广名称7',
fav: 0,
show: 5107,
weak: 6407,
signin: 4166,
click: 7970,
active: 1002,
day7: 8701,
day30: 9040,
tomorrow: 7632,
day: 4061,
week: 4359,
month: 3676
},
{
name: '推广名称8',
fav: 0,
show: 862,
weak: 6520,
signin: 6696,
click: 3209,
active: 6801,
day7: 6364,
day30: 6850,
tomorrow: 9408,
day: 2481,
week: 1479,
month: 2346
},
{
name: '推广名称9',
fav: 0,
show: 567,
weak: 5859,
signin: 128,
click: 6593,
active: 1971,
day7: 7596,
day30: 3546,
tomorrow: 6641,
day: 1611,
week: 5534,
month: 3190
},
{
name: '推广名称10',
fav: 0,
show: 3651,
weak: 1819,
signin: 4595,
click: 7499,
active: 7405,
day7: 8710,
day30: 5518,
tomorrow: 428,
day: 9768,
week: 2864,
month: 5811
}
],
expected: `
名称,展示,唤醒,登录,点击,激活,7日留存,30日留存,次日留存,日活跃,周活跃,月活跃
推广名称1,7302,5627,1563,4254,1438,274,285,1727,558,4440,5610
推广名称2,4720,4086,3792,8690,8470,8172,5197,1684,2593,2507,1537
推广名称3,7181,8007,8477,1879,16,2249,3450,377,1561,3219,1588
推广名称4,9911,8976,8807,8050,7668,1547,2357,7278,5309,1655,9043
推广名称5,934,1394,6463,5278,9256,209,3563,8285,1230,4840,9908
推广名称6,6856,1608,457,4949,2909,4525,6171,1920,1966,904,6851
推广名称7,5107,6407,4166,7970,1002,8701,9040,7632,4061,4359,3676
推广名称8,862,6520,6696,3209,6801,6364,6850,9408,2481,1479,2346
推广名称9,567,5859,128,6593,1971,7596,3546,6641,1611,5534,3190
推广名称10,3651,1819,4595,7499,7405,8710,5518,428,9768,2864,5811
`
};
export const csvB = {
columns: [
{
title: '姓名',
key: 'name'
},
{
title: '年龄',
key: 'age'
},
{
title: '地址',
key: 'address'
}
],
data: [
{
name: '王小明',
age: 18,
address: '北京市朝\n阳区芍药居'
},
{
name: '张小刚',
age: 25,
address: '北京市海,淀区西二旗'
},
{
name: '李小红',
age: 30,
address: '上海市浦东\r新区世纪大道'
},
{
name: '周小伟',
age: 26,
address: '深圳市南山区深南大道'
}
],
expected: `
"姓名";"年龄";"地址"
"王小明";"18";"北京市朝\n阳区芍药居"
"张小刚";"25";"北京市海,淀区西二旗"
"李小红";"30";"上海市浦东\r新区世纪大道"
"周小伟";"26";"深圳市南山区深南大道"
`
};

View file

@ -0,0 +1,47 @@
import { createVue, destroyVM } from '../util';
import { csvA, csvB } from './assets/table/csvData.js';
const cleanCSV = (str) => str.split('\n').map(s => s.trim()).filter(Boolean).join('\n');
describe('Table.vue', () => {
let vm;
afterEach(() => {
destroyVM(vm);
});
describe('CSV export', () => {
it('should export simple data to CSV - test A', done => {
vm = createVue({
template: '<div><Table :columns="columns" :data="data" ref="tableA" /></div>',
data() {
return csvA;
},
mounted() {
this.$refs.tableA.exportCsv({callback: data => {
expect(cleanCSV(data)).to.equal(cleanCSV(this.expected));
expect(cleanCSV(data).length > 0).to.equal(true);
done();
}});
}
});
});
it('should export data with commas and line breaks to CSV - test B', done => {
vm = createVue({
template: '<div><Table :columns="columns" :data="data" ref="tableB" /></div>',
data() {
return csvB;
},
mounted() {
this.$refs.tableB.exportCsv({separator: ';', quoted: true, callback: data => {
expect(cleanCSV(data)).to.equal(cleanCSV(this.expected));
expect(cleanCSV(data).length > 0).to.equal(true);
done();
}});
}
});
});
});
});