Files
aue/docs/superpowers/specs/2026-05-30-file-upload-task-optimization-design.md
2026-06-03 13:16:30 +08:00

21 KiB
Raw Blame History

文件上传与任务状态管理优化设计文档

方案选择A - 渐进式增强(用户体验优先)
版本V1.0
日期2026-05-30
状态:待审核


1. 设计概述

1.1 项目背景

基于《文件管理接口文档》V2.12.0)规范,对现有文件上传功能和文件列表下的任务向量化/出题状态组件进行系统性用户体验优化。

1.2 优化目标

  • 主要目标:提升用户交互体验,增强功能易用性和反馈机制
  • 次要目标:改善性能表现,优化错误处理流程
  • 非目标:不进行架构重构,不改变核心数据流,不引入新技术栈

1.3 设计原则

  1. KISS原则:保持简单,避免过度工程化
  2. 增量改进:每个优化点独立可回滚
  3. 向后兼容不破坏现有API和组件接口
  4. 用户驱动:所有改进围绕实际使用场景
  5. 渐进增强:在现有代码基础上添加功能,不重写

2. 当前实现分析

2.1 现有架构

前端组件结构:
├── FileSelector.vue          # 文件选择器用于AI对话中选择文件
├── exam/
│   ├── UploadTaskItem.vue    # 任务状态展示卡片
│   ├── composables/
│   │   ├── useUploadTasks.js # 任务状态管理逻辑
│   │   └── useTaskManager.js # 任务管理器
│   ├── GeneratePanel.vue     # 向量化/出题面板
│   └── ExamModuleContainer.vue
├── api/
│   └── file.js               # 文件API接口定义
└── views/
    └── ReaderPage.vue        # 文档阅读页面

2.2 已识别问题清单

P0 - 必须修复(影响核心功能)

问题ID 问题描述 影响范围 严重程度
P0-01 无上传进度条显示 文件上传体验 🔴
P0-02 错误状态无重试机制 任务失败处理 🔴
P0-03 轮询策略固定5秒间隔 性能浪费 🟡

P1 - 应该改进(显著提升体验)

问题ID 问题描述 影响范围 严重程度
P1-01 不支持拖拽上传 上传便捷性 🟡
P1-02 无前端文件验证 错误预防 🟡
P1-03 错误提示不够友好 用户理解 🟡
P1-04 不支持批量操作 效率提升 🟢
P1-05 移动端适配不完善 多设备支持 🟢

P2 - 可以优化(锦上添花)

问题ID 问题描述 影响范围 严重程度
P2-01 无状态变更通知提醒 用户感知 🟢
P2-02 缺少加载骨架屏 视觉体验 🟢
P2-03 任务列表无虚拟滚动 大列表性能 🟢

3. 详细设计方案

3.1 模块一:文件上传增强(优先级:🔴 最高)

3.1.1 功能需求

FR-01: 上传进度条
  • 描述:显示实时上传百分比和预计剩余时间
  • 触发条件:文件大小 > 1MB 或上传时间 > 2秒时自动显示
  • UI设计
    ┌─────────────────────────────────────┐
    │ 📄 制度管理办法.docx (2.3MB)         │
    │ ████████████░░░░░░ 65%  剩余 3秒    │
    └─────────────────────────────────────┘
    
FR-02: 拖拽上传支持
  • 描述:支持将文件从桌面/资源管理器拖拽到上传区域
  • 技术方案HTML5 Drag and Drop API
  • 交互细节
    • 拖拽悬停时:边框高亮 + 提示文字"释放以上传"
    • 拖拽离开时:恢复原状
    • 支持多文件同时拖拽
FR-03: 前端文件验证
  • 验证规则
    • 支持的文件类型:.docx, .pdf, .txt, .md, .xlsx, .pptx
    • 最大文件大小50MB可配置
    • 文件名长度≤200字符
    • 特殊字符检查(禁止 \ / : * ? " < > |
  • 错误提示
    • 类型不支持:" 不支持的文件格式,请上传 .docx/.pdf/.txt 等文档"
    • 文件过大:" 文件超过50MB限制请压缩后重试"
    • 其他错误:"⚠️ 文件验证失败:{具体原因}"
FR-04: 上传前预览
  • 预览信息
    • 文件图标(根据扩展名显示不同图标)
    • 文件名(高亮显示)
    • 文件大小格式化为KB/MB
    • 文件类型标签(如"Word文档"、"PDF文件"
FR-05: 友好的错误处理
  • 错误分类
    • 🌐 网络错误:连接超时、服务器无响应
    • 📁 文件错误:格式不支持、文件损坏
    • 🔐 权限错误:未登录、无上传权限
    • ⚙️ 服务器错误500内部错误、存储空间不足
  • 错误展示
    • 图标 + 标题 + 详细说明 + 操作建议
    • 示例:
      ⚠️ 上传失败
      
      原因:服务器响应超时(>60秒
      
      建议:
      • 检查网络连接是否正常
      • 尝试压缩文件后重新上传
      • 如持续失败,请联系管理员
      
      [重新上传] [取消]
      

3.1.2 技术实现方案

修改文件清单
文件路径 改动类型 改动量 说明
src/components/FileSelector.vue 修改 ~150行 添加拖拽、验证、进度条
src/api/file.js 修改 ~20行 添加onUploadProgress回调
src/utils/fileValidator.js 新增 ~80行 文件验证工具函数
src/components/ui/ProgressBar.vue 新增 ~60行 可复用进度条组件
src/components/ui/DropZone.vue 新增 ~90行 拖拽上传区域组件
核心代码示例

Axios进度监听 (src/api/file.js)

uploadFile: (formData, onProgress) => {
  return apiClient.post('/file/upload', formData, {
    headers: { 'Content-Type': 'multipart/form-data' },
    onUploadProgress: (progressEvent) => {
      if (onProgress && progressEvent.total) {
        const percentCompleted = Math.round(
          (progressEvent.loaded * 100) / progressEvent.total
        )
        onProgress({
          loaded: progressEvent.loaded,
          total: progressEvent.total,
          percent: percentCompleted
        })
      }
    }
  })
}

拖拽上传组件 (src/components/ui/DropZone.vue)

<template>
  <div 
    class="drop-zone"
    :class="{ 'is-dragging': isDragging }"
    @dragenter.prevent="onDragEnter"
    @dragover.prevent="onDragOver"
    @dragleave.prevent="onDragLeave"
    @drop.prevent="onDrop"
    @click="$refs.fileInput.click()"
  >
    <div v-if="!isDragging" class="drop-zone-content">
      <InboxOutlined class="drop-icon" />
      <p class="drop-text">拖拽文件到此处<span class="link">点击选择</span></p>
      <p class="drop-hint">支持 .docx .pdf .txt .xlsx .pptx最大 50MB</p>
    </div>
    <div v-else class="drop-zone-active">
      <span class="active-text">📥 释放文件以上传</span>
    </div>
    <input
      ref="fileInput"
      type="file"
      multiple
      :accept="acceptedTypes"
      @change="onFileSelected"
      style="display: none"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { InboxOutlined } from '@ant-design/icons-vue'

const props = defineProps({
  acceptedTypes: {
    type: String,
    default: '.docx,.pdf,.txt,.md,.xlsx,.pptx'
  },
  maxSize: {
    type: Number,
    default: 50 * 1024 * 1024 // 50MB
  }
})

const emit = defineEmits(['files-selected', 'error'])

const isDragging = ref(false)
const fileInput = ref(null)

function onDragEnter(e) {
  isDragging.value = true
}

function onDragOver(e) {
  e.dataTransfer.dropEffect = 'copy'
}

function onDragLeave(e) {
  if (!e.currentTarget.contains(e.relatedTarget)) {
    isDragging.value = false
  }
}

function onDrop(e) {
  isDragging.value = false
  const files = Array.from(e.dataTransfer.files)
  validateAndEmit(files)
}

function onFileSelected(e) {
  const files = Array.from(e.target.files)
  validateAndEmit(files)
  // 重置input以允许重复选择相同文件
  e.target.value = ''
}

function validateAndEmit(files) {
  const validFiles = []
  const errors = []

  files.forEach(file => {
    // 验证文件类型
    const ext = '.' + file.name.split('.').pop().toLowerCase()
    if (!props.acceptedTypes.includes(ext)) {
      errors.push({ file: file.name, reason: '不支持的文件格式' })
      return
    }

    // 验证文件大小
    if (file.size > props.maxSize) {
      errors.push({ file: file.name, reason: `文件过大 (${formatSize(file.size)},限制${formatSize(props.maxSize)})` })
      return
    }

    validFiles.push(file)
  })

  if (validFiles.length > 0) {
    emit('files-selected', validFiles)
  }

  if (errors.length > 0) {
    emit('error', errors)
  }
}
</script>

3.2 模块二:任务状态优化(优先级:🟡 高)

3.2.1 功能需求

FR-06: 一键重试按钮
  • 触发条件:任务状态为 error 时显示
  • 位置UploadTaskItem 组件的错误标签旁
  • 行为
    1. 点击后立即调用重新处理API
    2. 按钮变为加载状态(转圈图标)
    3. 成功后自动刷新任务列表
    4. 失败后显示错误详情
  • UI设计
    ❌ 向量化失败  [🔄 重试] [📋 详情]
    
FR-07: 批量删除功能
  • 使用场景:清理大量失败/已完成的历史任务
  • 交互流程
    1. 进入批量模式(勾选框出现)
    2. 选择多个任务(支持全选)
    3. 点击"删除选中项(N)"按钮
    4. 弹出确认对话框(显示即将删除的任务数)
    5. 执行删除并显示进度
  • 权限控制:仅管理员可见此功能
FR-08: Toast通知系统
  • 触发事件
    • 上传成功:" 文件「{filename}」上传成功,正在处理中..."
    • 向量化完成:"🎉 「{filename}」向量化完成,准备生成题目..."
    • 出题完成:"📝 「{filename}」已生成{count}道题目"
    • 处理失败:" 「{filename}」处理失败:{原因}"
  • 配置选项
    • 显示时长:成功=3秒失败=5秒
    • 位置:右下角
    • 可关闭:是
    • 堆叠方式垂直堆叠最多3条同时显示
FR-09: 智能轮询策略
  • 当前问题固定5秒轮询无论是否有活跃任务

  • 优化方案

    任务状态 轮询间隔 说明
    有处理中任务 3秒 快速反馈
    全部完成/失败 10秒 降低频率
    页面不可见 30秒 节省资源
    无任何任务 停止轮询 完全停止
  • 实现方式:使用 document.visibilitychange 事件检测页面可见性

FR-10: 加载骨架屏
  • 应用场景
    1. 首次加载任务列表时
    2. 刷新任务状态时(>500ms
  • UI效果:灰色脉冲动画块模拟真实内容布局
  • 组件复用:创建通用 SkeletonCard 组件

3.2.2 技术实现方案

修改文件清单
文件路径 改动类型 改动量 说明
src/components/exam/UploadTaskItem.vue 修改 ~40行 添加重试按钮
src/components/exam/composables/useUploadTasks.js 修改 ~80行 智能轮询+批量操作
src/components/ui/SkeletonCard.vue 新增 ~50行 骨架屏组件
src/components/ui/ToastNotification.vue 新增 ~90行 通知组件
src/utils/notification.js 新增 ~60行 通知工具函数
核心代码示例

智能轮询 (useUploadTasks.js 修改部分)

// 新增智能轮询配置
const POLL_CONFIG = {
  active: 3000,      // 有活跃任务时3秒
  idle: 10000,       // 全部空闲时10秒
  hidden: 30000,     // 页面隐藏时30秒
  maxInterval: 30000 // 最大间隔上限
}

let currentInterval = POLL_CONFIG.idle
let visibilityHandler = null

function updatePollingStrategy() {
  const hasActiveTasks = shouldPoll()
  const isHidden = document.hidden

  let newInterval
  if (isHidden) {
    newInterval = POLL_CONFIG.hidden
  } else if (hasActiveTasks) {
    newInterval = POLL_CONFIG.active
  } else {
    newInterval = POLL_CONFIG.idle
  }

  // 仅当间隔变化时才重启定时器
  if (newInterval !== currentInterval) {
    currentInterval = newInterval
    stopPolling()
    if (tasks.value.length > 0) {
      startPolling()
    }
    console.log(`[useUploadTasks] 轮询间隔调整为 ${currentInterval}ms`)
  }
}

// 监听页面可见性
function setupVisibilityListener() {
  if (visibilityHandler) return
  
  visibilityHandler = () => {
    updatePollingStrategy()
  }
  document.addEventListener('visibilitychange', visibilityHandler)
}

onMounted(() => {
  setupVisibilityListener()
})

onUnmounted(() => {
  if (visibilityHandler) {
    document.removeEventListener('visibilitychange', visibilityHandler)
  }
})

一键重试 (UploadTaskItem.vue 修改部分)

<template>
  <!-- 在错误标签旁添加重试按钮 -->
  <span v-if="vectorStatus.status === 'error'" class="uti-tag error">
    {{ vectorStatus.label }}
    <button class="retry-btn" @click="handleRetry" :disabled="retrying">
      <LoadingOutlined v-if="retrying" spin />
      <ReloadOutlined v-else />
      重试
    </button>
  </span>
</template>

<script setup>
import { ref } from 'vue'
import { ReloadOutlined, LoadingOutlined } from '@ant-design/icons-vue'

const props = defineProps({
  task: Object,
  vectorStatus: Object,
  examStatus: Object
})

const emit = defineEmits(['retry'])
const retrying = ref(false)

async function handleRetry() {
  retrying.value = true
  try {
    await emit('retry', props.task.id)
  } finally {
    setTimeout(() => { retrying.value = false }, 1000)
  }
}
</script>

3.3 模块三:移动端适配(优先级:🟢 中)

3.3.1 适配策略

断点定义
断点名称 宽度范围 目标设备
sm ≥640px 大屏手机横屏/小平板
md ≥768px 平板竖屏
lg ≥1024px 桌面端
关键改动
  1. UploadTaskItem 组件

    • 字体缩小12px → 11px
    • 状态标签换行显示
    • 错误信息默认展开(无需点击"详情"
  2. 文件列表

    • 卡片布局改为单列
    • 操作按钮改为底部固定栏
    • 添加下拉刷新手势支持
  3. 上传区域

    • 拖拽区域全屏宽度
    • 点击区域增大最小44px触控目标

4. 数据流与接口变更

4.1 API接口调整

新增接口(可选,如后端已支持则使用):

接口 方法 用途
/api/file/{id}/retry POST 重试失败任务
/api/file/batch-delete POST 批量删除任务

兼容性处理

如果后端暂不支持上述接口,前端降级方案:

  • 重试功能:调用原有上传接口重新上传同一文件
  • 批量删除循环调用单个删除接口带loading状态

4.2 数据流图

用户操作
  ↓
FileSelector.vue拖拽/选择文件)
  ↓
fileValidator.js前端验证
  ├─ 通过 → 显示预览 → 开始上传
  └─ 失败 → 显示友好错误提示
       ↓
api/file.js uploadFile带进度回调
  ↓
ProgressBar.vue实时更新进度
  ↓
上传完成
  ↓
useUploadTasks.js添加任务到列表
  ↓
智能轮询3-30秒动态调整
  ↓
UploadTaskItem.vue展示状态
  ├─ 处理中 → 动画图标
  ├─ 完成 → 成功图标 + Toast通知
  └─ 失败 → 错误图标 + 重试按钮

5. 性能与兼容性考虑

5.1 性能优化措施

  1. 按需加载:新组件使用动态导入

    const DropZone = defineAsyncComponent(() => import('./components/ui/DropZone.vue'))
    
  2. 防抖/节流

    • 搜索输入debounce 300ms
    • 窗口resizethrottle 150ms
    • 滚动事件passive listener
  3. 内存管理

    • 轮询定时器在组件卸载时清除
    • 大文件上传完成后释放引用
    • 图片/文件预览使用URL.revokeObjectURL()

5.2 浏览器兼容性

功能 最低版本 降级方案
Drag & Drop API IE10+, 所有现代浏览器 回退到点击上传
Progress Event IE10+, 所有现代浏览器 不显示进度条
Visibility API IE10+, Chrome 13+ 固定10秒轮询
CSS Grid/Flexbox IE11+ (partial), 现代浏览器全支持 使用float fallback

6. 测试计划

6.1 单元测试

测试场景 输入 预期输出 优先级
文件类型验证 test.exe 返回错误"不支持的格式" P0
文件大小验证 60MB文件 返回错误"超过50MB限制" P0
进度计算 loaded=50, total=100 percent=50 P0
拖拽事件处理 有效文件 触发files-selected事件 P1
轮询间隔调整 页面hidden 间隔变为30秒 P1
重试按钮点击 error状态任务 调用retry事件 P0

6.2 集成测试

  1. 上传流程测试

    • 选择文件 → 验证 → 上传 → 进度显示 → 完成
    • 拖拽文件 → 验证 → 上传 → 进度显示 → 完成
    • 选择无效文件 → 显示错误 → 修正后重试
  2. 任务状态测试

    • 上传后观察状态流转UPLOADED→VECTORIZING→VECTORIZED→EXAM_GENERATING→COMPLETED
    • 模拟失败场景 → 点击重试 → 验证重试逻辑
    • 批量选择 → 删除 → 确认删除成功
  3. 边界情况测试

    • 同时上传10个文件
    • 网络中断后恢复
    • 页面刷新后状态保持
    • 移动端触摸操作

6.3 兼容性测试

  • Chrome 90+
  • Firefox 88+
  • Safari 14+
  • Edge 90+
  • ⚠️ iOS Safari 14+(移动端)
  • ⚠️ Android Chrome 90+

7. 实施路线图

Phase 1核心功能第1天- 6小时

上午3小时

  • 创建 src/utils/fileValidator.js 文件验证工具
  • 创建 src/components/ui/ProgressBar.vue 进度条组件
  • 修改 src/api/file.js 添加进度回调支持

下午3小时

  • 创建 src/components/ui/DropZone.vue 拖拽组件
  • 修改 src/components/FileSelector.vue 集成新功能
  • 测试上传流程(正常/异常场景)

Phase 2任务状态优化第2天- 6小时

上午3小时

  • 修改 UploadTaskItem.vue 添加重试按钮
  • 修改 useUploadTasks.js 实现智能轮询
  • 创建 src/components/ui/ToastNotification.vue

下午3小时

  • 实现批量删除功能
  • 创建骨架屏组件
  • 测试任务状态流转

Phase 3打磨与测试第3天- 6小时

上午3小时

  • 移动端响应式适配
  • 跨浏览器测试
  • 性能 profiling

下午3小时

  • 用户体验走查
  • Bug修复
  • 文档编写

8. 风险评估与缓解

8.1 技术风险

风险 可能性 影响 缓解措施
后端不支持重试API 前端降级为重新上传
大文件上传内存溢出 使用分片上传Phase 2考虑
拖拽API兼容性问题 Feature detection + 降级方案
轮询性能开销 智能间隔 + 页面不可见时暂停

8.2 业务风险

风险 可能性 影响 缓解措施
用户不接受新交互 A/B测试 + 快速回滚能力
移动端体验不佳 充分的设备测试
与现有工作流冲突 保持向后兼容 + 渐进启用

9. 成功指标KPIs

9.1 定量指标

指标 当前值 目标值 测量方法
上传操作成功率 未知 ≥98% 错误日志统计
平均上传等待感知时间 未知 减少30% 用户调研
任务状态查询次数/会话 固定12次/分钟 动态3-30次 性能监控
错误重试成功率 0% ≥80% 重试按钮点击率

9.2 定性指标

  • 用户能够清晰了解上传进度
  • 任务失败时有明确的解决路径(重试按钮)
  • 移动端操作流畅无明显卡顿
  • 错误信息易于理解和行动

10. 附录

10.1 参考文档

10.2 术语表

术语 定义
向量化(Vectorize) 将文档内容转换为向量表示,用于语义搜索
出题(Exam Generation) 基于文档内容自动生成考试题目
轮询(Polling) 客户端定期向服务器请求最新状态的机制
骨架屏(Skeleton) 内容加载时的占位动画效果
Toast通知 短暂出现的消息提示,通常位于屏幕角落

文档结束

请审核本设计文档,确认后我们将进入实施阶段。