Files
Hua.Todo/docs/project/研发工单-v1.3.0/04-富文本描述与附件管理.md
ShaoHua 9223ceca50 feat(mcp): 新增 MCP 服务基础设施,重构规则文件序号,新增 v1.3.0 工单文档
- 规则重组:全局/ 下 8 个规则合并为 6 个(01+02→01,05+06→04),序号顺延

- 新增项目规则 05-多入口功能同步规范(UI/语音入口覆盖检查)

- 新增 MCP 服务基础设施:Mcp/ 目录(DI 注册、端点扩展、动态工具描述符)、单元测试

- v1.3.0 工单文档:03 系列(会议任务拆分)、04(富文本描述与附件管理)

- MCP 接口与前端集成指南:docs/manual/08、09
2026-06-16 01:15:40 +08:00

22 KiB
Raw Permalink Blame History

研发工单 v1.3.0 - 04 富文本描述、附件与外部链接

术语澄清:本文件中"研发工单"指智能体/开发者执行的编码工作项"任务/待办项/Todo"指 Hua.Todo 业务领域的 Todo 待办项Task 实体),二者请勿混淆。


一、目标与范围

1.1 目标

当前 Todo 待办项仅包含单行标题(Title),无法满足"一个待办项关联更多上下文信息"的需求。本工单为 Todo 待办项新增三项能力:

能力 描述
多行描述(Description 每个待办项可附带一段多行文字描述,用于记录详细说明、步骤、备注等
文件附件(Attachments 可为一个待办项关联多个本地文件,支持上传、查看、下载、删除
外部链接与程序启动 附件可以是外部 URL(点击在系统默认浏览器打开)或本地文件路径/可执行文件(点击通过系统关联程序打开)

1.2 平台范围

平台 是否支持 说明
WindowsMAUI 支持 全功能:描述编辑、附件管理、外部程序/链接打开
LinuxAvalonia 支持 全功能;xdg-open 打开外部资源
macOSMAUI 待定 视为 Windows 同级,但本期不单独投入
iOS / Android 不支持 移动端不提供附件/描述编辑入口,但移动端编辑待办项时不得覆盖/清空已有描述和附件数据(详见 1.4 节)

1.3 范围

包含

  • TaskEntity 新增 Description 字段(多行文本)
  • 新增 AttachmentEntity 数据模型(附件元数据)
  • 附件 CRUD API(上传、列表、下载、删除、打开)
  • 前端编辑对话框扩展(描述 textarea + 附件管理区域)
  • 前端待办项列表/详情展示(描述预览、附件数量角标)
  • 桌面端通过系统关联程序打开附件(URL → 浏览器,文件路径 → 关联应用)

不包含

  • 附件云同步(本期不涉及,后续版本规划)
  • 附件预览(如图片缩略图、PDF 内嵌预览,本期不做)
  • 移动端附件管理(本期仅桌面端)
  • 富文本编辑器(如 Markdown 渲染,本期仅纯文本多行)

1.4 跨平台数据安全:移动端编辑不得破坏已有数据(强制)

背景:移动端(iOS/Android)不提供描述/附件的编辑 UI,但用户仍可在移动端修改待办项标题、优先级、完成状态等基础字段。如果没有防护,移动端发起更新请求时会用"空值"覆盖掉桌面端已设置的 DescriptionAttachments,导致数据丢失。

约束

约束项 要求
后端 API 必须支持真正的部分更新 PUT /api/task/{id} 的请求体中,未传递的字段保持原值不变,不得将缺失字段视为"置空"。即:description 不在 JSON 中 → 不修改数据库中的 Descriptiondescriptionnull → 清空
UpdateTaskDto 所有扩展字段均为 optional descriptionattachments 相关字段在 DTO 中均标记为可选(string? / null),与必填字段(id)区分
移动端前端不传递未知字段 移动端构建 UpdateTaskDto 时只传 idtitlepriorityisCompleted,不传 descriptionattachments。后端对这些字段视为"不修改"
Attachment 实体独立于 Task 更新 附件 CRUD 走独立端点(/api/task/{id}/attachments),不通过 PUT /api/task/{id} 携带。移动端不调附件端点,自然不会破坏附件数据

验证方式

  1. 桌面端创建待办项,添加描述 + 上传附件
  2. 移动端编辑同一待办项(只改标题),保存
  3. 回到桌面端查看 → 描述和附件完整保留,未被覆盖

二、前置条件

条件 说明 状态
Hua.Todo v1.2.0 基础业务能力(Task CRUD、父子任务) 已完成
工单 03 TaskType 枚举 TaskEntity 已有字段扩展模式可参考 待实现(03 先于 04 或并行)
桌面 WebView 文件选择 前端 <input type="file"> 能力 已就绪
桌面程序启动 通过后端 Process.Start / xdg-open 实现 需新增

三、功能入口覆盖检查

依据 .trae/rules/项目/05-多入口功能同步规范.md,新功能必须检查两个入口覆盖情况。

入口 已规划 方案
UI 编辑对话框新增描述 textarea + 附件列表区域;TaskItem 卡片显示描述预览与附件数量角标
语音 ⚠️ 部分 语音可追加/编辑描述文本(通过"给「标题」添加描述"意图);附件上传与外部程序启动不适合语音入口

语音入口覆盖说明:

  • 支持:通过语音添加/修改待办项描述("给「XXX」添加备注:..."
  • 不支持:语音上传附件、语音打开外部程序 —— 两者本质上是可视化交互,语音不是合适的入口。本期不覆盖。
  • TODO:工单 02 的 LLM intent parser prompt 需预留 SET_DESCRIPTION 意图,参数为 targetTask + description

四、数据模型设计

4.1 TaskEntity 扩展

在现有 TaskEntity 上新增一个字段:

/// <summary>
/// 多行描述文本(可为空)。
/// 用于记录待办项相关的详细说明、步骤或备注。
/// </summary>
public string? Description { get; set; }
字段 类型 默认值 约束 说明
Description string? null 最大 5000 字符 多行描述文本

4.2 AttachmentEntity(新增)

Hua.Todo.Core/Entities/ 下新增 AttachmentEntity.cs

/// <summary>附件类型枚举</summary>
public enum AttachmentType
{
    /// <summary>本地文件(已复制到应用数据目录)</summary>
    LocalFile = 0,
    /// <summary>外部链接(URL</summary>
    ExternalLink = 1
}

/// <summary>附件实体,表示待办项关联的文件或外部链接</summary>
public class AttachmentEntity
{
    /// <summary>附件唯一标识符</summary>
    public int Id { get; set; }

    /// <summary>所属待办项ID</summary>
    public int TaskId { get; set; }

    /// <summary>显示名称(用户可见的文件名或链接标题)</summary>
    public string FileName { get; set; } = string.Empty;

    /// <summary>存储路径(本地文件的相对路径)或外部URL</summary>
    public string FilePath { get; set; } = string.Empty;

    /// <summary>文件大小(字节),外部链接为 0</summary>
    public long FileSize { get; set; }

    /// <summary>MIME 类型(如 text/plain、application/pdf),外部链接为空字符串</summary>
    public string ContentType { get; set; } = string.Empty;

    /// <summary>附件类型</summary>
    public AttachmentType AttachmentType { get; set; } = AttachmentType.LocalFile;

    /// <summary>创建时间(UTC</summary>
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

    // 导航属性
    public TaskEntity Task { get; set; } = null!;
}
字段 类型 默认值 说明
Id int 自增 主键
TaskId int - FK → Tasks.Id
FileName string - 显示名称,最大 256 字符
FilePath string - 存储路径或外部 URL,最大 1024 字符
FileSize long 0 字节数
ContentType string "" MIME 类型
AttachmentType AttachmentType LocalFile 本地文件 vs 外部链接
CreatedAt DateTime UtcNow 创建时间

TaskEntity 新增导航属性:

public List<AttachmentEntity> Attachments { get; set; } = new();

4.3 数据库表

表名 说明
T_Attachments 附件表

注:表名沿用 ABP 模板规范 T_{实体名}s(见 .trae/rules/项目/03-数据模型与迁移约束.md)。

4.4 附件存储策略

策略项 决定
存储位置 应用数据目录下 Attachments/ 子目录(与数据库 .db 同级)
文件命名 {attachmentId}_{originalFileName},避免重名冲突
文件大小上限 单文件 50MBappsettings.json 可配置)
总附件数上限 每个待办项最多 20 个附件
外部链接 不复制文件,仅存储 URL 字符串;打开时调用系统默认浏览器

五、API 设计

5.1 已修改:创建/更新待办项(扩展 Description

POST /api/task
请求体新增字段:
{
  "title": "...",
  "priority": 1,
  "description": "多行描述文本(可选)"   // ← 新增
}

PUT /api/task/{id}
请求体新增字段(所有扩展字段均为可选):
{
  "id": 42,
  "title": "...",
  "priority": 1,
  "description": "..."               // ← 可选;不传=保持原值,传 null=清空
}

部分更新语义(强制)PUT 端点必须实现"仅更新已传递字段"的逻辑。

  • description 字段不在 JSON 中 → 数据库 Description 保持原值
  • description 字段null → 数据库 Description 清空为 null
  • 此设计确保移动端不传递 description 时不会意外清空已有描述

5.2 附件上传

POST /api/task/{taskId}/attachments
Content-Type: multipart/form-data
字段:
  - file: 文件内容(必填,最大 50MB

响应体:
{
  "success": true,
  "data": {
    "id": 1,
    "taskId": 42,
    "fileName": "需求文档.pdf",
    "fileSize": 204800,
    "contentType": "application/pdf",
    "attachmentType": 0,
    "createdAt": "2026-06-16T10:00:00Z"
  }
}

5.3 添加外部链接

POST /api/task/{taskId}/attachments/link
请求体:
{
  "url": "https://example.com/doc",
  "fileName": "参考文档"    // 可选,不传则使用 URL 作为显示名
}

响应体:
{
  "success": true,
  "data": {
    "id": 2,
    "taskId": 42,
    "fileName": "参考文档",
    "filePath": "https://example.com/doc",
    "fileSize": 0,
    "contentType": "",
    "attachmentType": 1,
    "createdAt": "2026-06-16T10:00:00Z"
  }
}

5.4 获取附件列表

GET /api/task/{taskId}/attachments

响应体:
{
  "success": true,
  "data": [
    {
      "id": 1,
      "fileName": "需求文档.pdf",
      "fileSize": 204800,
      "contentType": "application/pdf",
      "attachmentType": 0,
      "createdAt": "2026-06-16T10:00:00Z"
    },
    {
      "id": 2,
      "fileName": "参考文档",
      "filePath": "https://example.com/doc",
      "attachmentType": 1,
      "createdAt": "2026-06-16T10:05:00Z"
    }
  ]
}

5.5 下载附件

GET /api/attachments/{attachmentId}/download

响应:文件流(Content-Disposition: attachment; filename="需求文档.pdf"

5.6 打开附件(桌面端)

POST /api/attachments/{attachmentId}/open

响应体:
{
  "success": true,
  "data": {
    "opened": true
  }
}

// 后端行为:
// - AttachmentType.LocalFile → Process.Start(filePath)Windows)或 xdg-openLinux
// - AttachmentType.ExternalLink → Process.Start(url)(在默认浏览器打开)

5.7 删除附件

DELETE /api/task/{taskId}/attachments/{attachmentId}

响应体:
{
  "success": true,
  "message": "附件已删除"
}

六、前端设计

6.1 类型扩展

src/Hua.Todo.Web/src/types/task.ts 新增 / 修改:

// Task 接口新增字段
export interface Task {
  // ... 既有字段
  description?: string;           // 多行描述
  attachments?: AttachmentItem[]; // 附件列表
  attachmentCount?: number;       // 附件数量(列表视图用,不传完整列表)
}

export interface AttachmentItem {
  id: number;
  taskId: number;
  fileName: string;
  filePath: string;
  fileSize: number;
  contentType: string;
  attachmentType: AttachmentType; // 0=本地文件, 1=外部链接
  createdAt: string;
}

export type AttachmentType = 0 | 1;

6.2 组件变更

组件 变更类型 说明
TaskEditDialog.vue 修改 新增描述 textarea + 附件管理区域(上传按钮、附件列表)
TaskItem.vue 修改 卡片底部显示描述预览(单行截断)+ 附件数量角标
AttachmentList.vue 新增 附件列表组件:文件名、大小、类型图标、删除按钮、打开按钮
LinkInputDialog.vue 新增 外部链接输入弹出框(URL + 显示名称)

6.3 新增 API 模块

src/Hua.Todo.Web/src/api/attachments.ts

// uploadAttachment(taskId, file)     — POST multipart/form-data
// addLink(taskId, url, fileName?)    — POST /api/task/{id}/attachments/link
// getAttachments(taskId)             — GET
// deleteAttachment(taskId, attId)    — DELETE
// openAttachment(attId)              — POST /api/attachments/{id}/open
// downloadAttachment(attId)          — GET /api/attachments/{id}/download

6.4 交互流程

编辑待办项对话框
├─ 标题输入框(现有)
├─ 优先级选择(现有)
├─ 描述 textarea(新增:多行文本,placeholder "添加备注、步骤说明...")
├─ 附件区域(新增)
│   ├─ [+ 上传文件] 按钮 → 触发文件选择器 → 上传至后端
│   ├─ [+ 添加链接] 按钮 → 弹出 URL 输入框 → 保存为外部链接附件
│   └─ 附件列表(每项显示:图标 + 文件名 + 大小 + [打开] [删除])
│       ├─ 本地文件:[打开] → 调用后端 open API → 系统关联程序打开
│       ├─ 外部链接:[打开] → 调用后端 open API → 浏览器打开
│       └─ [删除] → 确认 → 删除附件
└─ [取消] [保存]

6.5 列表卡片展示

┌─────────────────────────────────────┐
│ ☐ 整理产品需求文档                      │
│   高优先级                             │
│   需要在本周五前完成需求文档的整理...     │  ← 描述预览(单行,超长截断)
│   📎 2 个附件                          │  ← 附件数量角标
└─────────────────────────────────────┘

七、后端架构

7.1 新增服务

服务/类 位置 说明
AttachmentEntity Hua.Todo.Core/Entities/ 附件实体
AttachmentType Hua.Todo.Core/Entities/ 附件类型枚举
IAttachmentRepository Hua.Todo.Core/Repositories/ 仓储接口
AttachmentRepository Hua.Todo.Application/Data/ EF Core 仓储实现
AttachmentService Hua.Todo.Application/Services/ 附件业务逻辑(上传、下载、打开、清理)
IAttachmentService Hua.Todo.Application/Services/ 服务接口
AttachmentController Hua.Todo.Application/DynamicApi/ 或独立 Controller 附件动态 API 端点(或手动 Controller
PlatformAttachmentOpener Hua.Todo.{Maui,Avalonia}/Services/ 平台特定文件/链接打开实现(Process.Start / xdg-open

7.2 平台差异:外部程序启动

平台 实现方式
WindowsMAUI System.Diagnostics.Process.Start(new ProcessStartInfo { FileName = path, UseShellExecute = true })
LinuxAvalonia System.Diagnostics.Process.Start("xdg-open", path)

通过 IPlatformAttachmentOpener 接口在 Core 定义,MAUI / Avalonia 宿主各自实现并注册。

7.3 附件文件管理

  • 上传的本地文件复制到 <AppData>/Hua.Todo/Attachments/{attachmentId}_{originalFileName}
  • 删除附件时同步删除磁盘文件
  • 删除待办项时级联删除其所有附件(实体 + 文件)
  • 启动时验证附件文件完整性(数据库有记录但文件缺失 → 标记为失效,UI 提示)

7.4 安全约束

约束 说明
文件类型白名单 不限制(本地工具场景,用户自行管理)
文件大小上限 50MB(可配置)
路径遍历防护 文件名去除 ../..\ 等危险路径片段
外部链接验证 仅允许 http://https:// 协议

八、子工单拆分

本工单体积适中,不拆分子工单,单文件完整实现。

如需与工单 03 并行推进,注意以下共享文件:

共享文件 Writer 说明
TaskEntity.cs 工单 03 先写入 03 新增 TaskType/MeetingNotes/AudioDuration04 后续追加 Description/Attachments 导航属性
todoDbContext.cs 工单 03 先写入 03 新增 TaskType 映射;04 后续追加 Attachments DbSet + 关系映射
task.ts 工单 03 先写入 03 新增 taskType04 后追加 description、attachments
TaskEditDialog.vue 协调 03 新增 MeetingType 条件渲染;04 追加描述 + 附件区域

执行顺序:优先完成 03-01(数据模型),再开始 04 的后端模型部分。前端组件部分可独立并行。


九、验收标准

验收项 验证方法 预期结果
描述编辑 编辑待办项,在描述 textarea 输入多行文字,保存 再次打开编辑框,描述内容完整保留
描述显示 查看待办项列表/详情 列表卡片显示描述预览(单行截断),详情显示完整描述
附件上传 编辑待办项,点击上传文件,选择一个本地文件 文件上传成功,附件列表显示文件名和大小
附件下载 在附件列表点击下载按钮 文件以原始文件名下载到本地
件删除 点击附件删除按钮 附件从列表移除,磁盘文件同步清理
外部链接添加 点击添加链接,输入 URL 链接保存为附件,类型标记为"外部链接"
打开本地文件 点击本地附件"打开" 系统关联程序打开该文件(如 PDF → PDF 阅读器)
打开外部链接 点击外部链接附件"打开" 系统默认浏览器打开该 URL
打开可执行文件 附件为 .exeWindows)或可执行脚本(Linux 系统正常运行该程序
附件数量限制 超过 20 个附件后继续上传 提示"每个待办项最多 20 个附件"
文件大小限制 上传超过 50MB 的文件 提示"文件大小不能超过 50MB"
待办项删除级联 删除有附件的待办项 附件记录和磁盘文件同步清理
空描述 创建待办项时不填描述 正常创建,列表中不显示描述行
跨平台安全 桌面端设置描述+附件 → 移动端只改标题并保存 → 桌面端查看 描述和附件完整保留,未被覆盖(详见 1.4 节)

十、风险与回滚

风险 影响 应对策略
Process.Start 在不同 Linux 发行版行为差异 部分 Linux 文件打开失败 xdg-open 兜底;提供错误提示"无法打开文件:{原因}"
附件文件被用户手动删除 数据库有记录但文件不存在 启动时校验,缺失文件标记为"失效",UI 用灰色 + 感叹号提示
大文件上传耗尽磁盘空间 应用数据目录磁盘满 单文件 50MB + 每待办项 20 个上限 = 最多 1GB;检查和提示剩余空间
并发编辑附件冲突 多窗口同时删除/添加附件 附件操作为即时保存(非批量提交),利用数据库事务隔离

十一、Touch List

文件路径 修改类型 是否共享 说明
src/Hua.Todo.Core/Entities/TaskEntity.cs 修改 是(与 03 共享) 新增 Description 字段 + Attachments 导航属性
src/Hua.Todo.Core/Entities/AttachmentEntity.cs 新增 附件实体
src/Hua.Todo.Core/Entities/AttachmentType.cs 新增 附件类型枚举
src/Hua.Todo.Core/Repositories/IAttachmentRepository.cs 新增 附件仓储接口
src/Hua.Todo.Application/Data/TodoDbContext.cs 修改 是(与 03 共享) 新增 Attachments DbSet + 关系映射
src/Hua.Todo.Application/Services/IAttachmentService.cs 新增 附件服务接口
src/Hua.Todo.Application/Services/AttachmentService.cs 新增 附件业务逻辑
src/Hua.Todo.Application/Controllers/AttachmentController.cs 新增 附件 API 端点
src/Hua.Todo.Application/Models/AttachmentModels.cs 新增 附件 DTO
src/Hua.Todo.Core/Services/IPlatformAttachmentOpener.cs 新增 平台文件打开接口
src/Hua.Todo.Maui/Services/Platforms/MauiAttachmentOpener.cs 新增 Windows MAUI 文件打开实现
src/Hua.Todo.Avalonia/Services/Platforms/AvaloniaAttachmentOpener.cs 新增 Linux Avalonia 文件打开实现
src/Hua.Todo.Web/src/api/attachments.ts 新增 前端附件 API 模块
src/Hua.Todo.Web/src/types/task.ts 修改 是(与 03 共享) 新增 description、AttachmentItem 等类型
src/Hua.Todo.Web/src/components/TaskEditDialog.vue 修改 是(与 03 共享) 新增描述 textarea + 附件管理区域
src/Hua.Todo.Web/src/components/TaskItem.vue 修改 描述预览 + 附件角标
src/Hua.Todo.Web/src/components/AttachmentList.vue 新增 附件列表组件
src/Hua.Todo.Web/src/composables/useAttachments.ts 新增 附件管理组合式函数

十二、与工单 03 的交互

维度 工单 03 工单 04
描述文本 MeetingNotes(会议纪要,仅会议类型) Description(通用多行描述,所有类型)
附件 不涉及 通用附件管理
外部链接 不涉及 通用外部链接

MeetingNotesDescription 是两个独立字段:

  • MeetingNotes:会议转写/文字纪要,可能很长(数千字),仅在会议类型下有值
  • Description:通用简短备注(上限 5000 字符),所有类型的待办项都可以有

两者可共存:会议类型的待办项可以同时有 MeetingNotesAI 拆分的输入)和 Description(用户的简短备注)。


工单编号04 标题:富文本描述、附件与外部链接 版本v1.3.0 创建日期2026-06-16