// 文档管理应用 class DocumentManagementApp { constructor() { this.api = new DocumentAPI(); this.documents = []; this.currentFilter = { status: '', faultSource: '' }; this.currentPage = 0; this.pageSize = 20; this.selectedDocId = null; this.deleteTargetDocId = null; this.initializeElements(); this.bindEvents(); this.loadInitialData(); } initializeElements() { // 按钮 this.uploadBtn = document.getElementById('uploadBtn'); this.refreshBtn = document.getElementById('refreshBtn'); // 状态卡片 this.statCards = document.querySelectorAll('.stat-card'); // 筛选器 this.statusFilter = document.getElementById('statusFilter'); this.faultSourceFilter = document.getElementById('faultSourceFilter'); // 表格 this.documentsTableBody = document.getElementById('documentsTableBody'); this.documentsTable = document.getElementById('documentsTable'); this.emptyState = document.getElementById('emptyState'); // 详情面板 this.detailPanel = document.getElementById('detailPanel'); this.panelContent = document.getElementById('panelContent'); this.closePanelBtn = document.getElementById('closePanelBtn'); // 上传对话框 this.uploadModal = document.getElementById('uploadModal'); this.uploadForm = document.getElementById('uploadForm'); this.fileInput = document.getElementById('fileInput'); this.submitUploadBtn = document.getElementById('submitUploadBtn'); this.cancelUploadBtn = document.getElementById('cancelUploadBtn'); this.closeUploadModalBtn = document.getElementById('closeUploadModalBtn'); this.uploadModalBackdrop = document.getElementById('uploadModalBackdrop'); // 删除对话框 this.deleteModal = document.getElementById('deleteModal'); this.deleteMessage = document.getElementById('deleteMessage'); this.confirmDeleteBtn = document.getElementById('confirmDeleteBtn'); this.cancelDeleteBtn = document.getElementById('cancelDeleteBtn'); this.closeDeleteModalBtn = document.getElementById('closeDeleteModalBtn'); this.deleteModalBackdrop = document.getElementById('deleteModalBackdrop'); // 通知容器 this.notificationContainer = document.getElementById('notificationContainer'); } bindEvents() { // 上传按钮 this.uploadBtn.addEventListener('click', () => this.showUploadModal()); // 刷新按钮 this.refreshBtn.addEventListener('click', () => this.refreshList()); // 状态卡片点击 this.statCards.forEach(card => { card.addEventListener('click', () => { const status = card.dataset.status; this.applyStatusFilter(status); }); }); // 筛选器 this.statusFilter.addEventListener('change', () => { this.currentFilter.status = this.statusFilter.value; this.currentPage = 0; this.loadDocuments(); }); // 故障源筛选(防抖) let faultSourceTimeout; this.faultSourceFilter.addEventListener('input', () => { clearTimeout(faultSourceTimeout); faultSourceTimeout = setTimeout(() => { this.currentFilter.faultSource = this.faultSourceFilter.value.trim(); this.currentPage = 0; this.loadDocuments(); }, 300); }); // 详情面板关闭 this.closePanelBtn.addEventListener('click', () => this.closeDetailPanel()); // 上传对话框 this.uploadForm.addEventListener('submit', (e) => this.handleUpload(e)); this.cancelUploadBtn.addEventListener('click', () => this.hideUploadModal()); this.closeUploadModalBtn.addEventListener('click', () => this.hideUploadModal()); this.uploadModalBackdrop.addEventListener('click', () => this.hideUploadModal()); // 删除对话框 this.confirmDeleteBtn.addEventListener('click', () => this.handleDelete()); this.cancelDeleteBtn.addEventListener('click', () => this.hideDeleteModal()); this.closeDeleteModalBtn.addEventListener('click', () => this.hideDeleteModal()); this.deleteModalBackdrop.addEventListener('click', () => this.hideDeleteModal()); } async loadInitialData() { try { await Promise.all([ this.loadDocuments(), this.updateStats() ]); } catch (error) { console.error('初始化数据加载失败:', error); this.showError('数据加载失败: ' + error.message); } } async loadDocuments() { try { // 根据筛选条件加载文档 if (this.currentFilter.status) { this.documents = await this.api.getDocumentsByStatus( this.currentFilter.status, this.currentPage, this.pageSize ); } else if (this.currentFilter.faultSource) { this.documents = await this.api.getDocumentsByFaultSource( this.currentFilter.faultSource ); } else { // 默认加载所有已索引的文档 this.documents = await this.api.getDocumentsByStatus( 'INDEXED', this.currentPage, this.pageSize ); } this.renderDocuments(); } catch (error) { console.error('加载文档失败:', error); this.showError('加载文档失败: ' + error.message); } } renderDocuments() { if (!this.documents || this.documents.length === 0) { this.documentsTable.style.display = 'none'; this.emptyState.style.display = 'flex'; return; } this.documentsTable.style.display = 'table'; this.emptyState.style.display = 'none'; this.documentsTableBody.innerHTML = this.documents.map(doc => this.renderDocumentRow(doc) ).join(''); // 绑定操作按钮事件 this.documentsTableBody.querySelectorAll('.btn-view').forEach(btn => { btn.addEventListener('click', () => { const docId = btn.dataset.docId; this.showDocumentDetail(docId); }); }); this.documentsTableBody.querySelectorAll('.btn-delete').forEach(btn => { btn.addEventListener('click', () => { const docId = btn.dataset.docId; const fileName = btn.dataset.fileName; this.showDeleteConfirm(docId, fileName); }); }); } renderDocumentRow(doc) { return ` ${this.truncateText(doc.fileName, 30)} ${this.getFaultCategoryLabel(doc.faultCategory)} ${doc.faultSource || '-'} ${doc.apiName || '-'} ${doc.version} ${this.getStatusBadge(doc.status)} ${doc.chunkCount} ${this.formatDateTime(doc.createdAt)}
`; } getStatusBadge(status) { const badges = { PENDING: { text: '待处理', className: 'pending' }, PROCESSING: { text: '处理中', className: 'processing' }, INDEXED: { text: '已索引', className: 'indexed' }, FAILED: { text: '失败', className: 'failed' } }; const badge = badges[status] || badges.PENDING; return `${badge.text}`; } getFaultCategoryLabel(category) { const labels = { EXTERNAL_API: '外部接口', INTERNAL_ERROR: '内部错误', DATABASE: '数据库', CACHE: '缓存', NETWORK: '网络', THREAD: '线程', MEMORY: '内存', CONFIG: '配置' }; return labels[category] || category; } async updateStats() { try { const statuses = ['PENDING', 'PROCESSING', 'INDEXED', 'FAILED']; const results = await Promise.all( statuses.map(status => this.api.getDocumentsByStatus(status, 0, 999) ) ); statuses.forEach((status, index) => { const count = results[index] ? results[index].length : 0; const elementId = `stat${status.charAt(0) + status.slice(1).toLowerCase()}`; const element = document.getElementById(elementId); if (element) { element.textContent = count; } }); } catch (error) { console.error('更新统计失败:', error); } } applyStatusFilter(status) { this.statusFilter.value = status; this.currentFilter.status = status; this.currentFilter.faultSource = ''; this.faultSourceFilter.value = ''; this.currentPage = 0; this.loadDocuments(); } async refreshList() { this.refreshBtn.disabled = true; try { await Promise.all([ this.loadDocuments(), this.updateStats() ]); this.showSuccess('刷新成功'); } catch (error) { this.showError('刷新失败: ' + error.message); } finally { this.refreshBtn.disabled = false; } } // 上传对话框 showUploadModal() { this.uploadModal.classList.add('show'); this.uploadForm.reset(); } hideUploadModal() { this.uploadModal.classList.remove('show'); } async handleUpload(event) { event.preventDefault(); const file = this.fileInput.files[0]; if (!file) { this.showError('请选择文件'); return; } // 检查文件大小(10MB) if (file.size > 10 * 1024 * 1024) { this.showError('文件大小不能超过 10MB'); return; } // 构建 FormData const formData = new FormData(); formData.append('file', file); formData.append('faultCategory', document.getElementById('faultCategory').value); formData.append('faultSource', document.getElementById('faultSource').value); formData.append('apiName', document.getElementById('apiName').value); formData.append('version', document.getElementById('version').value); formData.append('chunkSize', document.getElementById('chunkSize').value); formData.append('chunkOverlap', document.getElementById('chunkOverlap').value); // 显示加载状态 this.submitUploadBtn.disabled = true; this.submitUploadBtn.querySelector('.btn-text').style.display = 'none'; this.submitUploadBtn.querySelector('.btn-loading').style.display = 'flex'; try { const docId = await this.api.uploadDocument(formData); this.showSuccess('文档上传成功'); this.hideUploadModal(); await this.refreshList(); } catch (error) { this.showError('上传失败: ' + error.message); } finally { this.submitUploadBtn.disabled = false; this.submitUploadBtn.querySelector('.btn-text').style.display = 'inline'; this.submitUploadBtn.querySelector('.btn-loading').style.display = 'none'; } } // 删除对话框 showDeleteConfirm(docId, fileName) { this.deleteTargetDocId = docId; this.deleteMessage.textContent = `确定删除文档 "${fileName}" 吗?`; this.deleteModal.classList.add('show'); } hideDeleteModal() { this.deleteModal.classList.remove('show'); this.deleteTargetDocId = null; } async handleDelete() { if (!this.deleteTargetDocId) return; this.confirmDeleteBtn.disabled = true; try { await this.api.deleteDocument(this.deleteTargetDocId); this.showSuccess('文档删除成功'); this.hideDeleteModal(); await this.refreshList(); } catch (error) { this.showError('删除失败: ' + error.message); } finally { this.confirmDeleteBtn.disabled = false; } } // 详情面板 async showDocumentDetail(docId) { this.selectedDocId = docId; this.detailPanel.classList.add('open'); // 显示加载状态 this.panelContent.innerHTML = `

加载中...

`; try { const doc = await this.api.getDocument(docId); this.renderDetailPanel(doc); } catch (error) { this.panelContent.innerHTML = `

加载失败: ${error.message}

`; } } renderDetailPanel(doc) { this.panelContent.innerHTML = `

基本信息

${doc.docId}
${doc.fileName}
${this.formatFileSize(doc.fileSize)}
${this.getStatusBadge(doc.status)}

分类信息

${this.getFaultCategoryLabel(doc.faultCategory)}
${doc.faultSource || '-'}
${doc.apiName || '-'}
${doc.version}

索引信息

${doc.chunkCount}
${this.formatDateTime(doc.indexedAt) || '-'}
${doc.status === 'FAILED' && doc.errorMessage ? `
${doc.errorMessage}
` : ''}

时间信息

${this.formatDateTime(doc.createdAt)}
`; } closeDetailPanel() { this.detailPanel.classList.remove('open'); this.selectedDocId = null; } // 工具函数 formatDateTime(dateTime) { if (!dateTime) return '-'; const date = new Date(dateTime); if (isNaN(date.getTime())) return '-'; const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } formatFileSize(bytes) { if (!bytes || 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]; } truncateText(text, maxLength) { if (!text) return '-'; if (text.length <= maxLength) return text; return text.substring(0, maxLength) + '...'; } // 通知 showSuccess(message) { this.showNotification(message, 'success'); } showError(message) { this.showNotification(message, 'error'); } showNotification(message, type) { const notification = document.createElement('div'); notification.className = `notification ${type}`; const icon = type === 'success' ? '' : ''; notification.innerHTML = `${icon}${message}`; this.notificationContainer.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); } } // API 调用层 class DocumentAPI { constructor() { this.baseUrl = '/api/documents'; } async uploadDocument(formData) { const response = await fetch(`${this.baseUrl}/upload`, { method: 'POST', body: formData }); return this.handleResponse(response); } async getDocument(docId) { const response = await fetch(`${this.baseUrl}/${docId}`); return this.handleResponse(response); } async getDocumentsByStatus(status, page = 0, size = 20) { const response = await fetch( `${this.baseUrl}/status/${status}?page=${page}&size=${size}` ); return this.handleResponse(response); } async getDocumentsByFaultSource(faultSource) { const response = await fetch( `${this.baseUrl}/faultSource/${encodeURIComponent(faultSource)}` ); return this.handleResponse(response); } async deleteDocument(docId) { const response = await fetch(`${this.baseUrl}/${docId}`, { method: 'DELETE' }); return this.handleResponse(response); } async handleResponse(response) { if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); if (result.code !== 200) { throw new Error(result.message || '请求失败'); } return result.data; } } // 初始化应用 document.addEventListener('DOMContentLoaded', () => { new DocumentManagementApp(); });