- 规则重组:全局/ 下 8 个规则合并为 6 个(01+02→01,05+06→04),序号顺延 - 新增项目规则 05-多入口功能同步规范(UI/语音入口覆盖检查) - 新增 MCP 服务基础设施:Mcp/ 目录(DI 注册、端点扩展、动态工具描述符)、单元测试 - v1.3.0 工单文档:03 系列(会议任务拆分)、04(富文本描述与附件管理) - MCP 接口与前端集成指南:docs/manual/08、09
22 KiB
研发工单 v1.3.0 - 04 富文本描述、附件与外部链接
术语澄清:本文件中"研发工单"指智能体/开发者执行的编码工作项;"任务/待办项/Todo"指 Hua.Todo 业务领域的 Todo 待办项(Task 实体),二者请勿混淆。
一、目标与范围
1.1 目标
当前 Todo 待办项仅包含单行标题(Title),无法满足"一个待办项关联更多上下文信息"的需求。本工单为 Todo 待办项新增三项能力:
| 能力 | 描述 |
|---|---|
| 多行描述(Description) | 每个待办项可附带一段多行文字描述,用于记录详细说明、步骤、备注等 |
| 文件附件(Attachments) | 可为一个待办项关联多个本地文件,支持上传、查看、下载、删除 |
| 外部链接与程序启动 | 附件可以是外部 URL(点击在系统默认浏览器打开)或本地文件路径/可执行文件(点击通过系统关联程序打开) |
1.2 平台范围
| 平台 | 是否支持 | 说明 |
|---|---|---|
| Windows(MAUI) | ✅ 支持 | 全功能:描述编辑、附件管理、外部程序/链接打开 |
| Linux(Avalonia) | ✅ 支持 | 全功能;xdg-open 打开外部资源 |
| macOS(MAUI) | 待定 | 视为 Windows 同级,但本期不单独投入 |
| iOS / Android | ❌ 不支持 | 移动端不提供附件/描述编辑入口,但移动端编辑待办项时不得覆盖/清空已有描述和附件数据(详见 1.4 节) |
1.3 范围
包含:
TaskEntity新增Description字段(多行文本)- 新增
AttachmentEntity数据模型(附件元数据) - 附件 CRUD API(上传、列表、下载、删除、打开)
- 前端编辑对话框扩展(描述 textarea + 附件管理区域)
- 前端待办项列表/详情展示(描述预览、附件数量角标)
- 桌面端通过系统关联程序打开附件(URL → 浏览器,文件路径 → 关联应用)
不包含:
- 附件云同步(本期不涉及,后续版本规划)
- 附件预览(如图片缩略图、PDF 内嵌预览,本期不做)
- 移动端附件管理(本期仅桌面端)
- 富文本编辑器(如 Markdown 渲染,本期仅纯文本多行)
1.4 跨平台数据安全:移动端编辑不得破坏已有数据(强制)
背景:移动端(iOS/Android)不提供描述/附件的编辑 UI,但用户仍可在移动端修改待办项标题、优先级、完成状态等基础字段。如果没有防护,移动端发起更新请求时会用"空值"覆盖掉桌面端已设置的 Description 和 Attachments,导致数据丢失。
约束:
| 约束项 | 要求 |
|---|---|
| 后端 API 必须支持真正的部分更新 | PUT /api/task/{id} 的请求体中,未传递的字段保持原值不变,不得将缺失字段视为"置空"。即:description 不在 JSON 中 → 不修改数据库中的 Description;description 为 null → 清空 |
| UpdateTaskDto 所有扩展字段均为 optional | description、attachments 相关字段在 DTO 中均标记为可选(string? / null),与必填字段(id)区分 |
| 移动端前端不传递未知字段 | 移动端构建 UpdateTaskDto 时只传 id、title、priority、isCompleted,不传 description、attachments。后端对这些字段视为"不修改" |
| Attachment 实体独立于 Task 更新 | 附件 CRUD 走独立端点(/api/task/{id}/attachments),不通过 PUT /api/task/{id} 携带。移动端不调附件端点,自然不会破坏附件数据 |
验证方式:
- 桌面端创建待办项,添加描述 + 上传附件
- 移动端编辑同一待办项(只改标题),保存
- 回到桌面端查看 → 描述和附件完整保留,未被覆盖
二、前置条件
| 条件 | 说明 | 状态 |
|---|---|---|
| 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},避免重名冲突 |
| 文件大小上限 | 单文件 50MB(appsettings.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-open(Linux)
// - 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 平台差异:外部程序启动
| 平台 | 实现方式 |
|---|---|
| Windows(MAUI) | System.Diagnostics.Process.Start(new ProcessStartInfo { FileName = path, UseShellExecute = true }) |
| Linux(Avalonia) | 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/AudioDuration;04 后续追加 Description/Attachments 导航属性 |
todoDbContext.cs |
工单 03 先写入 | 03 新增 TaskType 映射;04 后续追加 Attachments DbSet + 关系映射 |
task.ts |
工单 03 先写入 | 03 新增 taskType;04 后追加 description、attachments |
TaskEditDialog.vue |
协调 | 03 新增 MeetingType 条件渲染;04 追加描述 + 附件区域 |
执行顺序:优先完成 03-01(数据模型),再开始 04 的后端模型部分。前端组件部分可独立并行。
九、验收标准
| 验收项 | 验证方法 | 预期结果 |
|---|---|---|
| 描述编辑 | 编辑待办项,在描述 textarea 输入多行文字,保存 | 再次打开编辑框,描述内容完整保留 |
| 描述显示 | 查看待办项列表/详情 | 列表卡片显示描述预览(单行截断),详情显示完整描述 |
| 附件上传 | 编辑待办项,点击上传文件,选择一个本地文件 | 文件上传成功,附件列表显示文件名和大小 |
| 附件下载 | 在附件列表点击下载按钮 | 文件以原始文件名下载到本地 |
| 附件删除 | 点击附件删除按钮 | 附件从列表移除,磁盘文件同步清理 |
| 外部链接添加 | 点击添加链接,输入 URL | 链接保存为附件,类型标记为"外部链接" |
| 打开本地文件 | 点击本地附件"打开" | 系统关联程序打开该文件(如 PDF → PDF 阅读器) |
| 打开外部链接 | 点击外部链接附件"打开" | 系统默认浏览器打开该 URL |
| 打开可执行文件 | 附件为 .exe(Windows)或可执行脚本(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(通用多行描述,所有类型) |
| 附件 | 不涉及 | 通用附件管理 |
| 外部链接 | 不涉及 | 通用外部链接 |
MeetingNotes与Description是两个独立字段:
MeetingNotes:会议转写/文字纪要,可能很长(数千字),仅在会议类型下有值Description:通用简短备注(上限 5000 字符),所有类型的待办项都可以有两者可共存:会议类型的待办项可以同时有
MeetingNotes(AI 拆分的输入)和Description(用户的简短备注)。
工单编号:04 标题:富文本描述、附件与外部链接 版本:v1.3.0 创建日期:2026-06-16