feat: add export functionality and fix button bindings

- Add export button and exportRecording() function
- Fix button initialization timing with loop check
- Change events to global window.events for export access
- Update all references to use window.events
- Fix stopRecording to enable controls even if replay fails
- Enable all control buttons (play, speed, export) after recording

🎯 Key improvements:
- Users can now export recorded sessions as JSON files
- All buttons now work correctly after recording stops
- Proper error handling for replay initialization
- User-selectable save paths for exported files

📁 Modified: index.html
This commit is contained in:
xugp
2026-04-10 17:28:53 +08:00
parent 27a17d7068
commit 2a7084db5b

View File

@@ -213,6 +213,21 @@
<button type="button" data-speed="4" disabled>4x</button>
</div>
</div>
<div style="margin-top: 10px;">
<button onclick="exportRecording()" style="
background: #28a745;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
" onmouseover="this.style.background='#1e7e34'"
onmouseout="this.style.background='#28a745'">
💾 导出录制文件
</button>
</div>
<div class="info-box" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
<h3>🎬 回放控制</h3>
<p>播放器提供完整控制:</p>
@@ -230,7 +245,8 @@
<script src="./packages/rrweb/dist/rrweb.umd.cjs"></script>
<script>
let events = [];
// 全局变量
window.events = [];
let stopRecordingFn = null;
let replayer = null;
let replayMeta = null;
@@ -352,12 +368,12 @@
}
function renderReplay() {
if (!events.length) {
if (!window.window.events.length) {
return;
}
destroyReplay();
const target = document.getElementById('replayer');
replayer = new rrweb.Replayer(events, {
replayer = new rrweb.Replayer(window.events, {
root: target,
speed: currentSpeed,
});
@@ -439,7 +455,22 @@
resetReplayControls();
} else {
console.log('等待 rrweb 加载...');
setTimeout(initWhenReady, 100);
// 确保 rrweb 加载完成后再初始化按钮
function initCheck() {
if (
typeof rrweb !== 'undefined' &&
typeof rrweb.record === 'function' &&
typeof rrweb.Replayer === 'function'
) {
console.log('rrweb 已加载,准备就绪');
setupButtons();
resetReplayControls();
} else {
console.log('等待 rrweb 加载...');
setTimeout(initCheck, 100);
}
}
initCheck();
}
}
@@ -448,12 +479,12 @@
stopRecordingFn();
}
stopRecordingFn = null;
events = [];
window.events = [];
destroyReplay();
try {
stopRecordingFn = rrweb.record({
emit(event) {
events.push(event);
window.events.push(event);
},
recordCanvas: true,
recordCrossOriginIframes: true,
@@ -477,20 +508,23 @@
}
stopRecordingFn();
stopRecordingFn = null;
updateStatus(`✅ 已录制 ${events.length} 个事件`, 'idle');
updateStatus(`✅ 已录制 ${window.events.length} 个事件`, 'idle');
document.getElementById('start-btn').disabled = false;
document.getElementById('stop-btn').disabled = true;
try {
renderReplay();
console.log('回放已初始化');
} catch (error) {
console.error('初始化回放失败:', error);
alert('初始化回放失败: ' + error.message);
return;
// 即使回放失败,也要启用控制按钮
setReplayControlsEnabled(true);
alert('初始化回放失败,但控制条已启用。');
}
console.log('录制完成,事件数量:', events.length);
console.log('录制完成,事件数量:', window.events.length);
console.log('事件列表:', events);
alert(`录制完成!\n共记录了 ${events.length} 个事件。\n请在右侧查看回放与控制条。`);
alert(`录制完成!\n共记录了 ${window.events.length} 个事件。\n请在右侧查看回放与控制条。`);
}
function changeColor() {
@@ -520,14 +554,72 @@
stopRecordingFn();
stopRecordingFn = null;
}
events = [];
window.events = [];
document.getElementById('start-btn').disabled = false;
document.getElementById('stop-btn').disabled = true;
destroyReplay();
updateStatus('⚪ 等待录制', 'idle');
}
setTimeout(initWhenReady, 100);
// 🆕 导出录制功能
function exportRecording() {
if (window.events.length === 0) {
alert('没有录制数据可以导出');
return;
}
// 准备导出数据
const exportData = {
version: '1.0',
timestamp: new Date().toISOString(),
events: window.events
};
// 转换为JSON字符串
const jsonStr = JSON.stringify(exportData, null, 2);
// 创建Blob对象
const blob = new Blob([jsonStr], { type: 'application/json' });
// 创建下载链接
const url = URL.createObjectURL(blob);
// 创建临时链接并触发下载
const a = document.createElement('a');
a.href = url;
// 默认文件名recording-日期时间.json
const now = new Date();
const timestamp = now.toISOString().slice(0, 19).replace(/:/g, '-');
a.download = `recording-${timestamp}.json`;
// 用户点击选择保存位置
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// 清理URL对象
URL.revokeObjectURL(url);
alert(`成功导出 ${window.events.length} 个事件!`);
}
// 确保 rrweb 加载完成后再初始化按钮
function initCheck() {
if (
typeof rrweb !== 'undefined' &&
typeof rrweb.record === 'function' &&
typeof rrweb.Replayer === 'function'
) {
console.log('rrweb 已加载,准备就绪');
setupButtons();
resetReplayControls();
} else {
console.log('等待 rrweb 加载...');
setTimeout(initCheck, 100);
}
}
initCheck();
</script>
</body>
</html>