// 文档管理应用
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 = `
`;
}
}
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();
});