Files
SuperBizAgent-java/openspec/changes/archive/2026-07-05-doc-management-ui/design.md
T

628 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 文档管理页面开发 - 设计文档
## 1. 架构设计
### 1.1 整体架构
```
documents.html (独立页面)
├── HTML 结构
│ ├── 顶部导航栏
│ ├── 状态统计区域
│ ├── 操作工具栏
│ ├── 文档列表区域
│ └── 详情面板(滑出式)
├── CSS 样式(复用 styles.css + 少量定制)
└── JavaScript 逻辑
├── API 调用层
├── 状态管理
├── UI 渲染
└── 事件处理
```
### 1.2 页面结构
```html
<body>
<div class="app-layout">
<!-- 左侧导航(可选,或仅顶部导航) -->
<aside class="sidebar-mini">
<a href="index.html">返回主页</a>
<a href="documents.html" class="active">文档管理</a>
</aside>
<!-- 主内容区 -->
<main class="main-content">
<!-- 顶部导航栏 -->
<header class="page-header">
<h1>文档管理</h1>
<button id="uploadBtn">上传文档</button>
<button id="refreshBtn">刷新</button>
</header>
<!-- 状态统计卡片 -->
<section class="stats-cards">
<div class="stat-card" data-status="PENDING">
<span class="stat-label">待处理</span>
<span class="stat-value" id="statPending">0</span>
</div>
<div class="stat-card" data-status="PROCESSING">
<span class="stat-label">处理中</span>
<span class="stat-value" id="statProcessing">0</span>
</div>
<div class="stat-card" data-status="INDEXED">
<span class="stat-label">已索引</span>
<span class="stat-value" id="statIndexed">0</span>
</div>
<div class="stat-card" data-status="FAILED">
<span class="stat-label">失败</span>
<span class="stat-value" id="statFailed">0</span>
</div>
</section>
<!-- 操作工具栏 -->
<div class="toolbar">
<div class="filters">
<select id="statusFilter">
<option value="">全部状态</option>
<option value="PENDING">待处理</option>
<option value="PROCESSING">处理中</option>
<option value="INDEXED">已索引</option>
<option value="FAILED">失败</option>
</select>
<input type="text" id="faultSourceFilter" placeholder="按故障源筛选">
</div>
</div>
<!-- 文档列表 -->
<div class="documents-table-container">
<table class="documents-table">
<thead>
<tr>
<th>文件名</th>
<th>类别</th>
<th>故障源</th>
<th>接口名称</th>
<th>版本</th>
<th>状态</th>
<th>分块数</th>
<th>上传时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="documentsTableBody">
<!-- 动态生成 -->
</tbody>
</table>
<div class="pagination" id="pagination">
<!-- 分页控件 -->
</div>
</div>
</main>
<!-- 详情面板(右侧滑出) -->
<aside class="detail-panel" id="detailPanel">
<div class="panel-header">
<h2>文档详情</h2>
<button id="closePanelBtn">&times;</button>
</div>
<div class="panel-content" id="panelContent">
<!-- 动态生成 -->
</div>
</aside>
</div>
<!-- 上传对话框 -->
<div class="modal" id="uploadModal">
<div class="modal-content">
<h2>上传文档</h2>
<form id="uploadForm">
<div class="form-group">
<label>选择文件</label>
<input type="file" id="fileInput" required>
</div>
<div class="form-group">
<label>文档类别</label>
<select id="faultCategory">
<option value="EXTERNAL_API">外部接口调用失败</option>
<option value="INTERNAL_ERROR">系统内部错误</option>
<option value="DATABASE">数据库问题</option>
<option value="CACHE">缓存问题</option>
<option value="NETWORK">网络问题</option>
<option value="THREAD">线程问题</option>
<option value="MEMORY">内存问题</option>
<option value="CONFIG">配置问题</option>
</select>
</div>
<div class="form-group">
<label>故障源</label>
<input type="text" id="faultSource" placeholder="如:广东、order-service">
</div>
<div class="form-group">
<label>接口名称</label>
<input type="text" id="apiName" placeholder="如:社保查询、订单服务API">
</div>
<div class="form-group">
<label>版本</label>
<input type="text" id="version" value="v1.0">
</div>
<div class="form-group">
<label>分块大小</label>
<input type="number" id="chunkSize" value="500">
</div>
<div class="form-group">
<label>分块重叠</label>
<input type="number" id="chunkOverlap" value="50">
</div>
<div class="modal-actions">
<button type="submit" id="submitUploadBtn">上传</button>
<button type="button" id="cancelUploadBtn">取消</button>
</div>
</form>
</div>
</div>
<!-- 删除确认对话框 -->
<div class="modal" id="deleteModal">
<div class="modal-content">
<h2>确认删除</h2>
<p id="deleteMessage"></p>
<p class="warning">此操作将删除 MySQL 和 Milvus 中的所有数据,不可恢复。</p>
<div class="modal-actions">
<button id="confirmDeleteBtn" class="danger">删除</button>
<button id="cancelDeleteBtn">取消</button>
</div>
</div>
</div>
</body>
```
## 2. API 交互设计
### 2.1 API 响应格式
```json
{
"code": 200,
"message": "success",
"data": { ... },
"timestamp": 1719283200000
}
```
### 2.2 API 调用封装
```javascript
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) {
const result = await response.json();
if (result.code !== 200) {
throw new Error(result.message || '请求失败');
}
return result.data;
}
}
```
### 2.3 状态管理
```javascript
class DocumentManager {
constructor() {
this.api = new DocumentAPI();
this.documents = [];
this.currentFilter = { status: '', faultSource: '' };
this.currentPage = 0;
this.pageSize = 20;
this.selectedDocId = null;
}
async loadDocuments() {
// 根据筛选条件加载文档
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();
this.updateStats();
}
async updateStats() {
const statuses = ['PENDING', 'PROCESSING', 'INDEXED', 'FAILED'];
for (const status of statuses) {
const docs = await this.api.getDocumentsByStatus(status, 0, 999);
document.getElementById(`stat${status.charAt(0) + status.slice(1).toLowerCase()}`).textContent = docs.length;
}
}
}
```
## 3. UI 组件设计
### 3.1 状态徽章
```javascript
function getStatusBadge(status) {
const badges = {
PENDING: { text: '待处理', color: '#757575' },
PROCESSING: { text: '处理中', color: '#1a73e8' },
INDEXED: { text: '已索引', color: '#34a853' },
FAILED: { text: '失败', color: '#ea4335' }
};
const badge = badges[status] || badges.PENDING;
return `<span class="status-badge" style="background: ${badge.color}">${badge.text}</span>`;
}
```
### 3.2 文档列表行
```javascript
function renderDocumentRow(doc) {
return `
<tr data-doc-id="${doc.docId}" class="document-row">
<td>${doc.fileName}</td>
<td>${doc.faultCategory}</td>
<td>${doc.faultSource || '-'}</td>
<td>${doc.apiName || '-'}</td>
<td>${doc.version}</td>
<td>${getStatusBadge(doc.status)}</td>
<td>${doc.chunkCount}</td>
<td>${formatDateTime(doc.createdAt)}</td>
<td>
<button class="btn-view" data-doc-id="${doc.docId}">查看</button>
<button class="btn-delete" data-doc-id="${doc.docId}">删除</button>
</td>
</tr>
`;
}
```
### 3.3 详情面板
```javascript
function renderDetailPanel(doc) {
return `
<div class="detail-section">
<h3>基本信息</h3>
<div class="detail-item">
<label>文档ID:</label>
<span>${doc.docId}</span>
</div>
<div class="detail-item">
<label>文件名:</label>
<span>${doc.fileName}</span>
</div>
<div class="detail-item">
<label>文件大小:</label>
<span>${formatFileSize(doc.fileSize)}</span>
</div>
<div class="detail-item">
<label>状态:</label>
${getStatusBadge(doc.status)}
</div>
</div>
<div class="detail-section">
<h3>分类信息</h3>
<div class="detail-item">
<label>文档类别:</label>
<span>${doc.faultCategory}</span>
</div>
<div class="detail-item">
<label>故障源:</label>
<span>${doc.faultSource || '-'}</span>
</div>
<div class="detail-item">
<label>接口名称:</label>
<span>${doc.apiName || '-'}</span>
</div>
<div class="detail-item">
<label>版本:</label>
<span>${doc.version}</span>
</div>
</div>
<div class="detail-section">
<h3>索引信息</h3>
<div class="detail-item">
<label>分块数量:</label>
<span>${doc.chunkCount}</span>
</div>
<div class="detail-item">
<label>索引时间:</label>
<span>${formatDateTime(doc.indexedAt)}</span>
</div>
${doc.status === 'FAILED' ? `
<div class="detail-item error">
<label>错误信息:</label>
<span>${doc.errorMessage}</span>
</div>
` : ''}
</div>
<div class="detail-section">
<h3>时间信息</h3>
<div class="detail-item">
<label>创建时间:</label>
<span>${formatDateTime(doc.createdAt)}</span>
</div>
</div>
`;
}
```
## 4. 样式设计
### 4.1 核心样式变量(复用 styles.css)
```css
/* 复用现有变量 */
--primary-color: #1a73e8;
--background: #ffffff;
--surface: #f1f3f4;
--border: #dadce0;
--text: #202124;
--text-secondary: #5f6368;
```
### 4.2 文档管理特定样式
```css
/* 状态统计卡片 */
.stats-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background: #ffffff;
border: 1px solid #dadce0;
border-radius: 8px;
padding: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.stat-card:hover {
border-color: #1a73e8;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* 文档表格 */
.documents-table {
width: 100%;
border-collapse: collapse;
background: #ffffff;
border-radius: 8px;
overflow: hidden;
}
.documents-table th {
background: #f1f3f4;
padding: 12px;
text-align: left;
font-weight: 500;
border-bottom: 1px solid #dadce0;
}
.documents-table td {
padding: 12px;
border-bottom: 1px solid #f1f3f4;
}
.document-row:hover {
background: #f8f9fa;
}
/* 状态徽章 */
.status-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
color: #ffffff;
font-size: 12px;
font-weight: 500;
}
/* 详情面板 */
.detail-panel {
position: fixed;
top: 0;
right: -400px;
width: 400px;
height: 100vh;
background: #ffffff;
border-left: 1px solid #dadce0;
box-shadow: -2px 0 8px rgba(0,0,0,0.1);
transition: right 0.3s ease;
overflow-y: auto;
z-index: 1000;
}
.detail-panel.open {
right: 0;
}
```
## 5. 事件处理流程
### 5.1 上传文档流程
```
1. 用户点击"上传文档"按钮
↓
2. 显示上传表单对话框
↓
3. 用户选择文件并填写表单
↓
4. 点击"上传"按钮,触发表单提交
↓
5. 构建 FormData,调用 API
POST /api/documents/upload
↓
6. 显示加载状态(禁用按钮,显示加载图标)
↓
7. 成功:关闭对话框,刷新列表,高亮新文档
失败:显示错误信息,保持对话框打开
```
### 5.2 删除文档流程
```
1. 用户点击"删除"按钮
↓
2. 显示删除确认对话框
↓
3. 用户确认删除
↓
4. 调用 API
DELETE /api/documents/{docId}
↓
5. 成功:关闭对话框,刷新列表
失败:显示错误信息
```
### 5.3 筛选流程
```
1. 用户选择筛选条件
- 点击状态卡片
- 选择状态下拉框
- 输入故障源
↓
2. 更新 currentFilter
↓
3. 重置 currentPage = 0
↓
4. 调用 loadDocuments()
↓
5. 渲染新的文档列表
```
## 6. 错误处理
### 6.1 网络错误
```javascript
try {
const data = await api.uploadDocument(formData);
showSuccess('文档上传成功');
} catch (error) {
showError('上传失败: ' + error.message);
}
```
### 6.2 业务错误
```javascript
async handleResponse(response) {
const result = await response.json();
if (result.code !== 200) {
throw new Error(result.message || '请求失败');
}
return result.data;
}
```
### 6.3 用户提示
```javascript
function showError(message) {
// 显示顶部通知条
const notification = document.createElement('div');
notification.className = 'notification error';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
}
```
## 7. 性能优化
### 7.1 分页加载
- 每页 20 条记录
- 避免一次性加载所有文档
### 7.2 防抖处理
- 故障源输入框使用防抖(300ms)
- 避免频繁调用 API
### 7.3 缓存策略
- 状态统计数据缓存 5 秒
- 避免频繁刷新统计数据
## 8. 可访问性
- 按钮添加 aria-label
- 表格添加 caption
- 表单字段添加 label 关联
- 对话框添加 role="dialog" 和 aria-modal="true"
## 9. 浏览器兼容性
- 目标浏览器:Chrome 90+, Firefox 88+, Safari 14+
- 使用标准 Fetch API(无需 polyfill)
- 使用 ES6 语法(async/await, class, arrow function)
## 10. 测试场景
### 10.1 功能测试
- [ ] 上传文档(成功 / 失败)
- [ ] 查看文档列表
- [ ] 按状态筛选
- [ ] 按故障源筛选
- [ ] 查看文档详情
- [ ] 删除文档
- [ ] 刷新列表
- [ ] 分页切换
### 10.2 边界测试
- [ ] 空列表状态
- [ ] 大文件上传(接近 10MB)
- [ ] 网络超时
- [ ] 后端服务不可用
- [ ] 特殊字符文件名
- [ ] 中文故障源
### 10.3 用户体验测试
- [ ] 上传进度反馈
- [ ] 错误信息清晰
- [ ] 加载状态提示
- [ ] 删除二次确认
- [ ] 表单验证