- 创建统一的下载错误处理模块 (lib/download-errors.ts) - 实现13种错误类型分类和中文错误消息 - 后端统一使用 createDownloadError 创建标准化错误响应 - 前端统一使用 parseDownloadError 解析错误并显示 - 修复正式系统数据库Schema,添加产品全案相关字段和表 - 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段 - 创建 material_assets, api_keys, api_call_logs 表
225 lines
8.2 KiB
HTML
225 lines
8.2 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>检查IndexedDB中的上传状态</title>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
max-width: 800px;
|
|
margin: 50px auto;
|
|
padding: 20px;
|
|
}
|
|
.upload-item {
|
|
border: 1px solid #ddd;
|
|
padding: 15px;
|
|
margin: 10px 0;
|
|
border-radius: 5px;
|
|
background: #f9f9f9;
|
|
}
|
|
.upload-item h3 {
|
|
margin-top: 0;
|
|
}
|
|
.progress {
|
|
background: #e0e0e0;
|
|
border-radius: 3px;
|
|
height: 20px;
|
|
margin: 10px 0;
|
|
overflow: hidden;
|
|
}
|
|
.progress-bar {
|
|
background: #4caf50;
|
|
height: 100%;
|
|
transition: width 0.3s;
|
|
}
|
|
button {
|
|
background: #f44336;
|
|
color: white;
|
|
border: none;
|
|
padding: 8px 16px;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
margin-top: 10px;
|
|
}
|
|
button:hover {
|
|
background: #da190b;
|
|
}
|
|
.info {
|
|
color: #666;
|
|
font-size: 14px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>IndexedDB 上传状态检查</h1>
|
|
<p>这个工具可以检查浏览器IndexedDB中保存的分片上传状态</p>
|
|
|
|
<div id="status"></div>
|
|
<div id="uploads"></div>
|
|
|
|
<script>
|
|
const DB_NAME = 'chunk-upload-db';
|
|
const STORE_NAME = 'upload-states';
|
|
|
|
async function getDB() {
|
|
return new Promise((resolve, reject) => {
|
|
const request = indexedDB.open(DB_NAME, 1);
|
|
request.onerror = () => reject(request.error);
|
|
request.onsuccess = () => resolve(request.result);
|
|
request.onupgradeneeded = (event) => {
|
|
const db = event.target.result;
|
|
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
db.createObjectStore(STORE_NAME, { keyPath: 'fileId' });
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
async function getAllPendingUploads() {
|
|
try {
|
|
const db = await getDB();
|
|
const transaction = db.transaction([STORE_NAME], 'readonly');
|
|
const store = transaction.objectStore(STORE_NAME);
|
|
return new Promise((resolve) => {
|
|
const request = store.getAll();
|
|
request.onsuccess = () => resolve(request.result || []);
|
|
request.onerror = () => resolve([]);
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to get pending uploads:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async function deleteUploadState(fileId) {
|
|
try {
|
|
const db = await getDB();
|
|
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
const store = transaction.objectStore(STORE_NAME);
|
|
return new Promise((resolve, reject) => {
|
|
const request = store.delete(fileId);
|
|
request.onsuccess = () => resolve();
|
|
request.onerror = () => reject(request.error);
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to delete state:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function clearAllUploadStates() {
|
|
try {
|
|
const db = await getDB();
|
|
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
const store = transaction.objectStore(STORE_NAME);
|
|
return new Promise((resolve, reject) => {
|
|
const request = store.clear();
|
|
request.onsuccess = () => resolve();
|
|
request.onerror = () => reject(request.error);
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to clear states:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function formatBytes(bytes) {
|
|
if (bytes === 0) return '0 B';
|
|
const k = 1024;
|
|
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
}
|
|
|
|
function formatDate(timestamp) {
|
|
if (!timestamp) return '未知';
|
|
return new Date(timestamp).toLocaleString('zh-CN');
|
|
}
|
|
|
|
async function loadUploads() {
|
|
const statusDiv = document.getElementById('status');
|
|
const uploadsDiv = document.getElementById('uploads');
|
|
|
|
statusDiv.innerHTML = '<p>正在加载...</p>';
|
|
|
|
try {
|
|
const uploads = await getAllPendingUploads();
|
|
|
|
if (uploads.length === 0) {
|
|
statusDiv.innerHTML = '<p style="color: green;">✅ 没有未完成的上传</p>';
|
|
uploadsDiv.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
statusDiv.innerHTML = `<p style="color: orange;">⚠️ 发现 ${uploads.length} 个未完成的上传状态</p>`;
|
|
|
|
let html = '<h2>上传状态列表</h2>';
|
|
|
|
uploads.forEach((upload, index) => {
|
|
const uploadedCount = Array.isArray(upload.uploadedChunks)
|
|
? upload.uploadedChunks.length
|
|
: (upload.uploadedChunks?.size || 0);
|
|
const progress = upload.totalChunks > 0
|
|
? Math.round((uploadedCount / upload.totalChunks) * 100)
|
|
: 0;
|
|
|
|
html += `
|
|
<div class="upload-item">
|
|
<h3>上传 ${index + 1}: ${upload.fileName}</h3>
|
|
<div class="info">
|
|
<p><strong>文件ID:</strong> ${upload.fileId}</p>
|
|
<p><strong>文件大小:</strong> ${formatBytes(upload.fileSize)}</p>
|
|
<p><strong>上传进度:</strong> ${uploadedCount} / ${upload.totalChunks} 块</p>
|
|
<p><strong>上传ID:</strong> ${upload.uploadId || '未初始化'}</p>
|
|
<p><strong>分片大小:</strong> ${formatBytes(upload.chunkSize)}</p>
|
|
</div>
|
|
<div class="progress">
|
|
<div class="progress-bar" style="width: ${progress}%"></div>
|
|
</div>
|
|
<p>${progress}%</p>
|
|
<button onclick="deleteUpload('${upload.fileId}')">删除此状态</button>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
html += '<br><button onclick="clearAll()" style="background: #ff5722;">清除所有状态</button>';
|
|
|
|
uploadsDiv.innerHTML = html;
|
|
} catch (error) {
|
|
statusDiv.innerHTML = `<p style="color: red;">❌ 加载失败: ${error.message}</p>`;
|
|
}
|
|
}
|
|
|
|
window.deleteUpload = async function(fileId) {
|
|
if (!confirm('确定要删除这个上传状态吗?')) {
|
|
return;
|
|
}
|
|
try {
|
|
await deleteUploadState(fileId);
|
|
alert('删除成功');
|
|
loadUploads();
|
|
} catch (error) {
|
|
alert('删除失败: ' + error.message);
|
|
}
|
|
};
|
|
|
|
window.clearAll = async function() {
|
|
if (!confirm('确定要清除所有上传状态吗?这将删除所有未完成的上传记录。')) {
|
|
return;
|
|
}
|
|
try {
|
|
await clearAllUploadStates();
|
|
alert('清除成功');
|
|
loadUploads();
|
|
} catch (error) {
|
|
alert('清除失败: ' + error.message);
|
|
}
|
|
};
|
|
|
|
// 页面加载时自动加载
|
|
loadUploads();
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|