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
|
@ -164,43 +164,47 @@
|
|||
<div class="layout" :class="{'layout-hide-text': spanLeft < 5}">
|
||||
<Row type="flex">
|
||||
<Col :span="spanLeft" class="layout-menu-left">
|
||||
<Menu active-name="1" theme="dark" width="auto">
|
||||
<div class="layout-logo-left"></div>
|
||||
<MenuItem name="1">
|
||||
<Icon type="ios-navigate" :size="iconSize"></Icon>
|
||||
<span class="layout-text">选项 1</span>
|
||||
</MenuItem>
|
||||
<MenuItem name="2">
|
||||
<Icon type="ios-keypad" :size="iconSize"></Icon>
|
||||
<span class="layout-text">选项 2</span>
|
||||
</MenuItem>
|
||||
<MenuItem name="3">
|
||||
<Icon type="ios-analytics" :size="iconSize"></Icon>
|
||||
<span class="layout-text">选项 3</span>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Menu active-name="1" theme="dark" width="auto">
|
||||
<div class="layout-logo-left"></div>
|
||||
<MenuItem name="1">
|
||||
<Icon type="ios-navigate" :size="iconSize"></Icon>
|
||||
<span class="layout-text">选项 1</span>
|
||||
</MenuItem>
|
||||
<MenuItem name="2">
|
||||
<Icon type="ios-keypad" :size="iconSize"></Icon>
|
||||
<span class="layout-text">选项 2</span>
|
||||
</MenuItem>
|
||||
<MenuItem name="3">
|
||||
<Icon type="ios-analytics" :size="iconSize"></Icon>
|
||||
<span class="layout-text">选项 3</span>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Col>
|
||||
<Col :span="spanRight">
|
||||
<div class="layout-header">
|
||||
<Button type="text" @click="toggleClick">
|
||||
<Icon type="navicon" size="32"></Icon>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="layout-breadcrumb">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbItem href="#">首页</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#">应用中心</BreadcrumbItem>
|
||||
<BreadcrumbItem>某应用</BreadcrumbItem>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
<div class="layout-content">
|
||||
<div class="layout-content-main">
|
||||
<Table stripe :columns="columns1" :data="data1"></Table>
|
||||
<div class="layout-header">
|
||||
<Button type="text" @click="toggleClick">
|
||||
<Icon type="navicon" size="32"></Icon>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="layout-breadcrumb">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbItem href="#">首页</BreadcrumbItem>
|
||||
<BreadcrumbItem href="#">应用中心</BreadcrumbItem>
|
||||
<BreadcrumbItem>某应用</BreadcrumbItem>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
<div class="layout-content">
|
||||
<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>
|
||||
</div>
|
||||
<div class="layout-copy">
|
||||
2011-2016 © TalkingData
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
@ -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>
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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,27 +43,17 @@ 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);
|
||||
}
|
||||
}
|
||||
|
|
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