console
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSV Stream 导出示例</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 20px auto;
padding: 0 20px;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f4f4f4;
}
button {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
.message {
margin-top: 10px;
padding: 10px;
border-radius: 4px;
}
.success {
background-color: #dff0d8;
color: #3c763d;
}
.error {
background-color: #f2dede;
color: #a94442;
}
#progress {
width: 100%;
height: 20px;
background-color: #f0f0f0;
border-radius: 4px;
margin-top: 10px;
display: none;
}
#progress-bar {
width: 0%;
height: 100%;
background-color: #4CAF50;
border-radius: 4px;
transition: width 0.3s ease;
}
</style>
</head>
<body>
<h1>表格数据导出示例 (Stream API)</h1>
<table id="dataTable">
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>城市</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td>张三</td>
<td>25</td>
<td>北京</td>
<td>测试数据1</td>
</tr>
<tr>
<td>李四</td>
<td>30</td>
<td>上海</td>
<td>包含,逗号</td>
</tr>
<tr>
<td>王五</td>
<td>28</td>
<td>广州</td>
<td>包含"引号"</td>
</tr>
</tbody>
</table>
<button onclick="handleExport()">导出CSV</button>
<div id="progress">
<div id="progress-bar"></div>
</div>
<div id="message"></div>
<script>
function getTableData() {
const table = document.getElementById('dataTable');
const headers = [];
const data = [];
const headerRow = table.querySelector('thead tr');
headerRow.querySelectorAll('th').forEach(th => {
headers.push(th.textContent);
});
table.querySelectorAll('tbody tr').forEach(tr => {
const rowData = {};
tr.querySelectorAll('td').forEach((td, index) => {
rowData[headers[index]] = td.textContent;
});
data.push(rowData);
});
return { headers, data };
}
function createCSVRow(values) {
return values.map(value => {
if (typeof value === 'string' && (value.includes(',') || value.includes('"') || value.includes('\n'))) {
return `"${value.replace(/"/g, '""')}"`;
}
return `"${value}"`;
}).join(',') + '\n';
}
function createCSVStream(headers, data) {
const encoder = new TextEncoder();
let currentRow = 0;
const totalRows = data.length + 1;
return new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode('\uFEFF'));
const headerRow = createCSVRow(headers);
controller.enqueue(encoder.encode(headerRow));
},
pull(controller) {
if (currentRow < data.length) {
const row = data[currentRow];
const values = headers.map(header => row[header] ?? '');
const csvRow = createCSVRow(values);
controller.enqueue(encoder.encode(csvRow));
updateProgress((currentRow + 2) / totalRows * 100);
currentRow++;
} else {
controller.close();
updateProgress(100);
}
}
});
}
function updateProgress(percentage) {
const progressBar = document.getElementById('progress-bar');
progressBar.style.width = `${percentage}%`;
}
function toggleProgress(show) {
const progress = document.getElementById('progress');
progress.style.display = show ? 'block' : 'none';
if (show) {
updateProgress(0);
}
}
function showMessage(text, isError = false) {
const messageDiv = document.getElementById('message');
messageDiv.textContent = text;
messageDiv.className = `message ${isError ? 'error' : 'success'}`;
setTimeout(() => {
messageDiv.textContent = '';
messageDiv.className = 'message';
}, 3000);
}
async function handleExport() {
try {
toggleProgress(true);
const { headers, data } = getTableData();
const readableStream = createCSVStream(headers, data);
const fileHandle = await window.showSaveFilePicker({
suggestedName: 'table-data.csv',
types: [{
description: 'CSV File',
accept: {
'text/csv': ['.csv'],
},
}],
});
const writableStream = await fileHandle.createWritable();
await readableStream
.pipeThrough(new TransformStream())
.pipeTo(writableStream);
showMessage('CSV导出成功!');
} catch (error) {
console.error('导出CSV失败:', error);
showMessage('导出CSV失败!', true);
} finally {
toggleProgress(false);
}
}
</script>
</body>
</html>