基于vue2+Element UI 的csv数据导入和展示组件,主要功能包括:
1、文件上传:用户可以通过点击按钮选择并上传CSV文件
2、CSV解析:自动解析csv文件内容,提取表头和数据
3、数据展示:将解析后的数据显示在Element UI表格中,效果与原始csv文件完全一致
4、数据导出:提供解析后的结构化数据,便于后续业务处理
具体代码:
1
utils/importCsv.js
/**
* CSV文件解析工具函数
* 使用xlsx库解析CSV文件并返回结构化数据
*/
import * as XLSX from 'xlsx';
/**
* 解析CSV文件的Blob或File对象
* @param {Blob|File} file - CSV文件对象
* @param {Object} options - 解析选项
* @param {number} options.maxRows - 最大解析行数,默认10000
* @param {boolean} options.hasHeader - 是否包含表头,默认true
* @returns {Promise<Object>} 解析结果包含headers和data
*/
export async function parseCsvFile(file, options = {}) {
const {
maxRows = 10000,
hasHeader = true
} = options;
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, {
type: 'array',
sheetRows: maxRows
});
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
// Convert to JSON array with raw values
const jsonData = XLSX.utils.sheet_to_json(worksheet, {
header: 1,
raw: true,
defval: ''
});
if (jsonData.length === 0) {
resolve({ headers: [], data: [], rawData: [] });
return;
}
let headers = [];
let dataRows = [];
if (hasHeader && jsonData.length > 0) {
// First row as headers
headers = jsonData[0].map((header, index) => {
return String(header || `列${index + 1}`);
});
dataRows = jsonData.slice(1);
} else {
// No headers, generate column names
const colCount = Math.max(...jsonData.map(row => row.length));
headers = Array.from({ length: colCount }, (_, i) => `列${i + 1}`);
dataRows = jsonData;
}
// Format data for table display
const formattedData = dataRows.map(row => {
const rowData = {};
headers.forEach((_, index) => {
rowData['col' + index] = row[index] !== undefined ? row[index] : '';
});
return rowData;
});
resolve({
headers,
data: formattedData,
rawData: dataRows
});
} catch (error) {
console.error('CSV解析错误:', error);
reject(new Error('CSV文件解析失败'));
}
};
reader.onerror = () => {
reject(new Error('文件读取失败'));
};
reader.readAsArrayBuffer(file);
});
}
/**
* 将CSV字符串解析为结构化数据
* @param {string} csvString - CSV格式的字符串
* @param {Object} options - 解析选项
* @returns {Object} 解析结果包含headers和data
*/
export function parseCsvString(csvString, options = {}) {
const {
hasHeader = true,
delimiter = ','
} = options;
try {
// Create a temporary workbook from CSV string
const workbook = XLSX.read(csvString, {
type: 'string',
cellDates: true
});
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
const jsonData = XLSX.utils.sheet_to_json(worksheet, {
header: 1,
raw: true,
defval: ''
});
if (jsonData.length === 0) {
return { headers: [], data: [], rawData: [] };
}
let headers = [];
let dataRows = [];
if (hasHeader && jsonData.length > 0) {
headers = jsonData[0].map((header, index) => {
return String(header || `列${index + 1}`);
});
dataRows = jsonData.slice(1);
} else {
const colCount = Math.max(...jsonData.map(row => row.length));
headers = Array.from({ length: colCount }, (_, i) => `列${i + 1}`);
dataRows = jsonData;
}
const formattedData = dataRows.map(row => {
const rowData = {};
headers.forEach((_, index) => {
rowData['col' + index] = row[index] !== undefined ? row[index] : '';
});
return rowData;
});
return {
headers,
data: formattedData,
rawData: dataRows
};
} catch (error) {
console.error('CSV字符串解析错误:', error);
throw new Error('CSV字符串解析失败');
}
}
export default {
parseCsvFile,
parseCsvString
};
2
components/CsvImport.vue
<template>
<div class="csv-import-container">
<!-- Upload button -->
<el-upload
ref="upload"
:action="uploadAction"
:auto-upload="false"
:show-file-list="false"
:on-change="handleFileChange"
:accept="'.csv'"
class="csv-upload"
>
<el-button type="primary" icon="el-icon-upload">
{{ uploadButtonText }}
</el-button>
</el-upload>
<!-- Display parsed CSV data as table -->
<div v-if="csvData.length > 0" class="csv-table-container">
<el-table
:data="csvData"
border
style="width: 100%"
:header-cell-style="{ background: '#f5f7fa', color: '#606266', 'text-align': 'center' }"
:cell-style="{ 'text-align': 'left' }"
>
<el-table-column
v-for="(header, index) in headers"
:key="index"
:prop="'col' + index"
:label="header"
:width="getColumnWidth(header)"
>
</el-table-column>
</el-table>
</div>
<!-- Empty state -->
<div v-else class="empty-state">
<i class="el-icon-document"></i>
<p>请上传CSV文件查看数据</p >
</div>
</div>
</template>
<script>
import * as XLSX from 'xlsx';
export default {
name: 'CsvImport',
props: {
uploadButtonText: {
type: String,
default: '导入CSV表格'
}
},
data() {
return {
uploadAction: '#', // Prevent actual upload
csvData: [],
headers: []
};
},
methods: {
handleFileChange(file, fileList) {
const fileName = file.name;
const fileExtension = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
if (fileExtension !== '.csv') {
this.$message.error('请上传CSV格式文件');
this.$refs.upload.clearFiles();
return;
}
// Read the file
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array', sheetRows: 10000 }); // Limit rows for performance
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
// Convert to JSON array
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
if (jsonData.length === 0) {
this.$message.warning('CSV文件为空');
this.csvData = [];
this.headers = [];
return;
}
// Extract headers (first row)
this.headers = jsonData[0].map((header, index) => {
return header || `列${index + 1}`;
});
// Process data rows
const dataRows = jsonData.slice(1);
this.csvData = dataRows.map(row => {
const rowData = {};
this.headers.forEach((_, index) => {
rowData['col' + index] = row[index] || '';
});
return rowData;
});
// Emit the parsed data for parent component usage
this.$emit('csv-imported', {
headers: this.headers,
data: this.csvData,
rawData: jsonData.slice(1)
});
this.$message.success('CSV文件导入成功');
} catch (error) {
console.error('CSV解析错误:', error);
this.$message.error('CSV文件解析失败,请检查文件格式');
this.csvData = [];
this.headers = [];
}
};
reader.onerror = () => {
this.$message.error('文件读取失败');
this.csvData = [];
this.headers = [];
};
reader.readAsArrayBuffer(file.raw);
},
getColumnWidth(header) {
// Calculate column width based on header length, with reasonable limits
const baseWidth = 100;
const charWidth = 12;
const headerLength = String(header).length;
const calculatedWidth = baseWidth + (headerLength * charWidth);
return Math.min(Math.max(calculatedWidth, 80), 300); // Min 80px, Max 300px
},
// Method to clear imported data
clearData() {
this.csvData = [];
this.headers = [];
if (this.$refs.upload) {
this.$refs.upload.clearFiles();
}
},
// Method to get current CSV data
getCsvData() {
return {
headers: this.headers,
data: this.csvData
};
}
}
};
</script>
<style scoped lang="scss">
.csv-import-container {
.csv-upload {
margin-bottom: 20px;
::v-deep .el-upload {
display: inline-block;
}
}
.csv-table-container {
margin-top: 16px;
::v-deep .el-table {
font-size: 12px;
.el-table__header th {
font-weight: bold;
}
.el-table__body td {
word-break: break-word;
white-space: pre-wrap;
}
}
}
.empty-state {
text-align: center;
padding: 40px 0;
color: #909399;
i {
font-size: 48px;
margin-bottom: 16px;
color: #c0c4cc;
}
p {
margin: 0;
font-size: 14px;
}
}
}
</style>
3
views/CsvImportDemo.vue
<template>
<div class="csv-import-demo">
<h2>CSV表格导入示例</h2>
<p>点击下方按钮上传CSV文件,页面将显示与CSV表格中完全一致的数据效果</p >
<CsvImport
ref="csvImport"
upload-button-text="选择CSV文件导入"
@csv-imported="handleCsvImported"
/>
<!-- Additional info panel -->
<div v-if="importInfo" class="import-info">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>导入信息</span>
</div>
<div>
<p><strong>文件列数:</strong> {{ importInfo.headers.length }}</p >
<p><strong>数据行数:</strong> {{ importInfo.data.length }}</p >
<p><strong>列名:</strong> {{ importInfo.headers.join(', ') }}</p >
</div>
<div style="margin-top: 16px;">
<el-button type="danger" size="small" @click="clearImport">清空数据</el-button>
</div>
</el-card>
</div>
</div>
</template>
<script>
import CsvImport from '@/components/CsvImport.vue';
export default {
name: 'CsvImportDemo',
components: {
CsvImport
},
data() {
return {
importInfo: null
};
},
methods: {
handleCsvImported(data) {
this.importInfo = data;
console.log('CSV导入完成:', data);
},
clearImport() {
this.importInfo = null;
this.$refs.csvImport.clearData();
}
}
};
</script>
<style scoped lang="scss">
.csv-import-demo {
padding: 20px;
h2 {
margin-bottom: 16px;
color: #303133;
}
p {
margin-bottom: 24px;
color: #606266;
line-height: 1.5;
}
.import-info {
margin-top: 24px;
::v-deep .el-card__body {
padding: 16px;
p {
margin: 8px 0;
font-size: 14px;
}
}
}
}
</style>

2893

被折叠的 条评论
为什么被折叠?



