Merge pull request #2114 from SergioCrisostomo/improve-csv
Improve export to CSV functionality
This commit is contained in:
commit
cbe5ccfc8a
5 changed files with 449 additions and 63 deletions
|
@ -197,9 +197,13 @@
|
||||||
<div class="layout-content-main">
|
<div class="layout-content-main">
|
||||||
<Table stripe :columns="columns1" :data="data1"></Table>
|
<Table stripe :columns="columns1" :data="data1"></Table>
|
||||||
</div>
|
</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>
|
||||||
<div class="layout-copy">
|
|
||||||
2011-2016 © TalkingData
|
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -330,8 +334,44 @@
|
||||||
age: 26,
|
age: 26,
|
||||||
address: '深圳市南山区深南大道'
|
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: {
|
computed: {
|
||||||
iconSize () {
|
iconSize () {
|
||||||
|
@ -347,7 +387,14 @@
|
||||||
this.spanLeft = 5;
|
this.spanLeft = 5;
|
||||||
this.spanRight = 19;
|
this.spanRight = 19;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
exportCSV () {
|
||||||
|
this.$refs.csvTable.exportCsv({
|
||||||
|
filename: '原始数据',
|
||||||
|
separator: ';',
|
||||||
|
quoted: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -714,8 +714,9 @@
|
||||||
let noHeader = false;
|
let noHeader = false;
|
||||||
if ('noHeader' in params) noHeader = params.noHeader;
|
if ('noHeader' in params) noHeader = params.noHeader;
|
||||||
|
|
||||||
const data = Csv(columns, datas, ',', noHeader);
|
const data = Csv(columns, datas, params, noHeader);
|
||||||
ExportCsv.download(params.filename, data);
|
if (params.callback) params.callback(data);
|
||||||
|
else ExportCsv.download(params.filename, data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
|
|
|
@ -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 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;
|
let columnOrder;
|
||||||
const content = [];
|
const content = [];
|
||||||
const column = [];
|
const column = [];
|
||||||
|
|
||||||
if (columns) {
|
if (columns) {
|
||||||
columnOrder = columns.map(v => {
|
columnOrder = columns.map(v => {
|
||||||
if (typeof v === 'string') {
|
if (typeof v === 'string') return v;
|
||||||
return v;
|
|
||||||
}
|
|
||||||
if (!noHeader) {
|
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;
|
return v.key;
|
||||||
});
|
});
|
||||||
if (column.length > 0) {
|
if (column.length > 0) appendLine(content, column, options);
|
||||||
content.push(column.join(separator));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
columnOrder = [];
|
columnOrder = [];
|
||||||
datas.forEach(v => {
|
datas.forEach(v => {
|
||||||
|
@ -29,26 +43,16 @@ export default function csv(columns, datas, separator = ',', noHeader = false) {
|
||||||
});
|
});
|
||||||
if (columnOrder.length > 0) {
|
if (columnOrder.length > 0) {
|
||||||
columnOrder = columnOrder.filter((value, index, self) => self.indexOf(value) === index);
|
columnOrder = columnOrder.filter((value, index, self) => self.indexOf(value) === index);
|
||||||
|
if (!noHeader) appendLine(content, columnOrder, options);
|
||||||
if (!noHeader) {
|
|
||||||
content.push(columnOrder.join(separator));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(datas)) {
|
if (Array.isArray(datas)) {
|
||||||
datas.map(v => {
|
datas.forEach(row => {
|
||||||
if (Array.isArray(v)) {
|
if (!Array.isArray(row)) {
|
||||||
return v;
|
row = columnOrder.map(k => (typeof row[k] !== 'undefined' ? row[k] : ''));
|
||||||
}
|
}
|
||||||
return columnOrder.map(k => {
|
appendLine(content, row, options);
|
||||||
if (typeof v[k] !== 'undefined') {
|
|
||||||
return v[k];
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
}).forEach(v => {
|
|
||||||
content.push(v.join(separator));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return content.join(newLine);
|
return content.join(newLine);
|
||||||
|
|
287
test/unit/specs/assets/table/csvData.js
Normal file
287
test/unit/specs/assets/table/csvData.js
Normal 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";"深圳市南山区深南大道"
|
||||||
|
`
|
||||||
|
};
|
47
test/unit/specs/table.spec.js
Normal file
47
test/unit/specs/table.spec.js
Normal 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();
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue