小说上传中心与异步处理进度展示设计

本阶段我的主要工作是开发小说上传中心,让用户可以把 TXT 小说提交到后端,并在前端看到处理进度。我把本阶段的重点放在三个方面:上传前校验、上传接口封装、上传后任务状态轮询。

相关代码主要在 frontend/src/views/UploadCenterView.vuefrontend/src/api/novel.ts。接口层中,我定义了 NovelUploadPayloadUploadRecord 等类型,让页面在调用接口时能明确知道需要传什么、后端会返回什么。上传小说时使用 FormData,因为文件不能像普通 JSON 一样直接提交。

复制代码
export function uploadNovel(payload: NovelUploadPayload) {
  const formData = new FormData()
  formData.append('file', payload.file)
  formData.append('title', payload.title)

  if (payload.author) formData.append('author', payload.author)
  if (payload.source) formData.append('source', payload.source)
  if (payload.copyrightStatus) formData.append('copyrightStatus', payload.copyrightStatus)
  if (payload.language) formData.append('language', payload.language)
  if (payload.description) formData.append('description', payload.description)

  return request.post('/api/v1/novels/upload', formData, {
    headers: {
      'Content-Type': 'multipart/form-data',
    },
  })
}

这里我没有把 FormData 逻辑写在页面组件里,而是放在 api/novel.ts 中。这样页面只关心"我要上传一本小说",不用关心 multipart 请求如何组织。这个拆分降低了页面组件的复杂度,也让接口调用更容易维护。

页面层的状态比较多:selectedFile 保存用户选择的文件,uploading 控制提交按钮状态,pageError 显示错误,latestNovelIdlatestUploadRecordId 记录后端返回的任务信息,uploadRecord 保存当前处理进度,pollingTimer 管理轮询定时器。这些状态分别对应用户操作、网络请求、后端任务和生命周期清理。

在上传前,我做了三类校验:是否选择文件、是否为 .txt 文件、标题是否为空。

复制代码
if (!selectedFile.value) {
  pageError.value = '请先选择一个 TXT 文件。'
  return
}
if (!selectedFile.value.name.toLowerCase().endsWith('.txt')) {
  pageError.value = '当前只支持上传 TXT 格式文件。'
  return
}
if (!uploadForm.title.trim()) {
  pageError.value = '请输入小说标题。'
  return
}

为了减少用户输入成本,我在 onFileChange 中做了一个小优化:如果用户还没有填写标题,就自动用文件名去掉扩展名作为默认标题。

复制代码
if (file && !uploadForm.title.trim()) {
  uploadForm.title = file.name.replace(/\.[^/.]+$/, '')
}

上传成功后,后端并不会立即完成全部知识构建流程,而是返回 novelIduploadRecordId。这时前端要根据 uploadRecordId 持续查询处理状态。我采用 setTimeout 而不是 setInterval,因为每次轮询都应该等上一次请求结束后再安排下一次,避免网络慢时多个请求堆积。

复制代码
function schedulePoll(uploadRecordId: string) {
  clearPolling()
  pollingTimer.value = window.setTimeout(() => {
    void refreshUploadRecord(uploadRecordId, true)
  }, 1400)
}

refreshUploadRecord 中会请求上传记录,如果任务还没有结束,就继续安排下一轮轮询。是否结束由 isDone 计算属性判断:

复制代码
const isDone = computed(() => {
  const status = uploadRecord.value?.status
  return status ? ['success', 'failed', 'cancelled'].includes(status) : false
})

本阶段调试时,我重点检查了三个问题。第一,后端接口返回后,前端是否能正确拿到 uploadRecordId 并启动轮询。第二,任务完成后是否停止轮询,避免请求无限持续。第三,上传失败或状态刷新失败时,页面是否能显示错误而不是静默失败。通过这些调试,我理解到异步任务页面和普通表单页面的区别:普通表单提交后只关心一次响应,而异步任务页面要关心任务的完整生命周期。

从工作量上看,本阶段我完成了小说上传页面的表单设计、文件选择、自动标题填充、前端校验、上传接口联调、处理状态轮询、状态文字映射、错误提示和生命周期清理。这个页面连接了前端用户入口和后端知识构建流程,是后续首页展示小说、选择剧情节点和开始游戏的前置条件。它的意义不只是"能上传文件",而是让用户能够看见一个长任务从提交到完成的全过程。

相关推荐
Marst Code2 小时前
⚙️ 2026 年推荐技术方案
前端
qq_366086222 小时前
测试接口传参数时,放在Header和Body中后台接收参数的区别
java·开发语言·前端
whatever who cares2 小时前
Vue3中vue文件和composables的分工
前端·javascript·vue.js
袋鼠云数栈UED团队2 小时前
基于 superpowers 实现复杂前端改造
前端
袋鼠云数栈前端2 小时前
基于 superpowers 实现复杂前端改造
前端·ai
sugar__salt2 小时前
LLM服务HTTP接口实战:前端HTTP请求全解与项目落地
前端·网络协议·http
薛先生_0992 小时前
vue-编程式跳转-基本跳转
前端·javascript·vue.js
微三云、小叶2 小时前
排队免单系统底层设计:四种分配算法拆解,无预支资金的合规营销架构方案
java·前端·软件开发·商业模式·本地生活·商业思维
copyer_xyf3 小时前
Python 内存分析:从栈和堆理解对象引用
前端·后端·python