22 KiB
Upload 桌面版架构设计
本文档定义 Upload 桌面版的设计定位、公共契约、状态模型、视觉主题关系和兼容边界。通用控件研发约束见 控件研发标准,内部实现原理见 Upload 桌面版实现原理,拖动输入专项契约见 Upload 拖动上传设计,Upload Token 的专项设计见 Upload Token 设计,设计和契约变化记录见 Upload Changelog。
1. 控件定位
| 项 | 值 |
|---|---|
| NuGet 包 | AtomUI.Desktop.Controls |
| .NET 命名空间 | AtomUI.Desktop.Controls |
| AXAML 命名空间 | https://atomui.net |
| Gallery 页面 | controlgallery/AtomUIGallery/ShowCases/DataEntry/Upload |
| 控件状态 | Stable |
Upload 是 AtomUI 桌面控件体系中的上传协调控件,用于管理文件选择、目录选择、拖拽提交、任务状态、列表展示、Form 值投影和上传操作入口。
Upload 不负责具体网络传输协议、文件存储服务或业务附件模型。这些职责由业务层或 IFileUploadTransport 承担。
主要源码入口:
src/AtomUI.Desktop.Controls/Uploadsrc/AtomUI.Controls.Shared/Netcontrolgallery/AtomUIGallery/ShowCases/DataEntry/Upload
2. 设计语言
Upload 的设计语言围绕“单一文件状态 owner + 可组合操作入口 + 独立列表视图”组织,而不是围绕某个固定模板节点组织。
| 维度 | 含义 | Upload 中的表达 |
|---|---|---|
| 产品语义 | 控件在界面中承担的稳定职责。 | Upload 是上传状态协调器,统一管理文件、队列、选择入口、拖拽入口和列表视图。 |
| 内容承载 | 用户数据、展示内容、集合项或操作入口如何进入控件。 | Files 保存真实文件项;TriggerContent、UploadTrigger、UploadDropZone 和 UploadList 负责组合展示。 |
| 状态反馈 | public API、内部状态和伪类如何形成用户可感知反馈。 | UploadFileItem.Status、Progress、ErrorMessage、Result 是任务状态来源;Form 错误走 DataValidationErrors。 |
| 主题语义 | ControlTheme、SharedToken、控件 Token 和模板绑定如何表达视觉。 | Upload Token + ControlTheme;滚动边界在 UploadList 内部,并使用 AtomUI 自动隐藏滚动条。 |
3. API 与契约模型
Upload 的公共契约由 public/protected 类型成员、Avalonia 属性、事件、命令、template part、伪类、ControlTheme key 和资源 key 共同组成。维护时应先确认这些契约是否已经被源码、Gallery 示例或文档暴露。
3.1 单一状态 owner
Upload 以 Files 作为唯一上传文件状态 owner。触发器、拖拽区和列表只是组合视图或动作入口,不保存第二份任务状态。
Upload.Files表示所有可观察上传文件。UploadTrigger通过SourceKind=Files|Directories选择文件或目录。UploadDropZone独占拖动协商和 Drop 快照,并把输入批次提交给最近的Upload。UploadDefaultDropArea只提供默认视觉,不读取拖动数据或维护文件状态。UploadList渲染Files,并独立管理滚动区域。PictureCard/PictureCircle的上传入口通过 append slot 呈现,不作为UploadFileItem。
3.2 核心 public surface
| 契约组 | 代表成员 | 维护含义 |
|---|---|---|
| 文件状态 | Files、UploadFileItem |
唯一文件状态 owner,支持绑定、Form 投影和列表渲染。 |
| 文件选择 | UploadTrigger、UploadSourceKind、SelectFilesAsync、SelectDirectoriesAsync |
文件与目录选择是独立动作入口;IsMultipleEnabled 统一决定用户是否可提交多个顶层 StorageItem。 |
| 拖拽提交 | UploadDropZone、UploadDirectoryDropMode、UploadDragState |
DropZone 负责平台协商和快照,Upload 负责统一准入与文件状态。 |
| 用户输入范围 | IsMultipleEnabled |
统一限制文件选择、目录选择和 Drop 的顶层 StorageItem 数量,不限制单个目录的文件展开结果或显式程序化批量输入。 |
| 文件准入 | AllowedFileTypes、CountOverflowBehavior、AdmissionPolicy |
picker、directory、drop 和 programmatic 输入共享同一准入与数量语义。 |
| 输入结果 | InputBatchCompleted、UploadInputBatchCompletedEventArgs |
每个输入批次在 UI 线程统一报告接受项、拒绝项以及 Completed、Cancelled 或 Failed 终态。 |
| 文件内容 | UploadFileInfo、IUploadFileSource |
Transport 通过可打开内容源读取文件,不假定本地路径可访问。 |
| 上传队列 | UploadTransport、AutoUpload、MaxConcurrentTasks、UploadQueue |
上传调度与视觉控件解耦,生命周期由 Upload 统一释放。 |
| 列表展示 | UploadList、ListType、ListMaxHeight、ListScrollBarVisibility |
列表内部滚动,触发区保持固定。 |
| 触发入口 | TriggerContent、UploadTrigger、Picture append slot |
文件/目录触发器由用户布局组合,PictureCard/PictureCircle 通过显示源 append slot 呈现。 |
| 状态反馈 | SuccessAutoRemoveDelay、PendingText、FileValueMode |
成功自动移除、待上传文案和 Form 值投影可配置。 |
| 视觉与动效 | IsMotionEnabled、Upload Token |
只表达视觉状态,不保存业务任务状态。 |
输入与准入 API 的默认契约:
| 成员 | 默认值 | 语义 |
|---|---|---|
Upload.AllowedFileTypes |
null |
不按文件名 pattern 或 MIME 拒绝文件;空集合含义相同。 |
Upload.CountOverflowBehavior |
RejectExcess |
按候选稳定顺序接受剩余容量内的文件,并拒绝超出项。 |
Upload.AdmissionPolicy |
null |
不执行额外业务准入;非空策略固定在非 UI 执行上下文调用。 |
Upload.MaxCount |
int.MaxValue |
输入管线可提交的最大 effective 文件数。 |
Upload.IsMultipleEnabled |
false |
文件选择器只选择一个文件、目录选择器只选择一个目录,Drop 只处理第一个顶层 StorageItem;单个目录仍可展开多个文件。 |
UploadDropZone.IsOpenFileDialogOnClick |
true |
主指针点击 DropZone 时按 SourceKind 打开选择器。 |
UploadDropZone.SourceKind |
Files |
点击时打开文件选择器。 |
UploadDropZone.IsFileDropEnabled |
true |
DropZone 参与文件 DragDrop 协商。 |
UploadDropZone.DirectoryDropMode |
Reject |
外部拖入目录时拒绝目录。 |
UploadDropZone.MaxDirectoryDepth |
32 |
递归目录根深度为 0,属性值不得小于 0。 |
UploadDropZone.MaxEnumeratedItems |
10000 |
每个顶层目录树最多观察的文件和目录总数,属性值必须大于 0。 |
UploadDropZone.DragState |
None |
只读拖动协商状态。 |
UploadDropZone.IsDropProcessing |
false |
只读活动 Drop 批次状态。 |
Upload.EnqueueFilesAsync、文件选择器、目录选择器和拖动输入分别产生 Programmatic、FilePicker、DirectoryPicker 和 DragDrop 批次。每个实际启动的批次只触发一次 InputBatchCompleted;事件在输入处理进入终态、剩余批次资源完成释放且接受项已经提交后于 UI 线程触发。正常批次在事件后完成 Task,取消批次在事件后传播 OperationCanceledException,失败批次在事件后传播原始异常或清理聚合异常。
IsMultipleEnabled 只约束用户输入批次的顶层 StorageItem。值为 false 时,文件和目录 picker 都使用 AllowMultiple=false,Drop 按快照稳定顺序处理第一个顶层项目,并以 MultipleSelectionNotAllowed 拒绝和释放其余项目;第一个项目是目录时,目录内部仍按 DirectoryDropMode 展开多个文件。值为 true 时,picker 与 Drop 均允许多个顶层项目。EnqueueFilesAsync(IEnumerable<UploadFileInfo>) 是显式程序化批量 API,不受该属性限制,但仍受文件准入、MaxCount 和 CountOverflowBehavior 约束。
UploadInputBatchStatus 通过 Completed、Cancelled 和 Failed 区分互斥终态。只有 Failed 携带 UploadInputFailureReason;取消和批次级失败不再构造虚假的 rejected item。UploadRejectedItem 只公开稳定的 UploadRejectionReason、业务 rejection code 和业务 message,不公开平台 Exception。UploadAdmissionDecision 通过 Accept() 和 Reject(...) 创建,不能构造“接受但携带拒绝信息”的矛盾状态。
UploadFileInfo 是不可变输入描述。Name 和 Source 必须存在;Path、Size、ContentType、DateCreated 与 DateModified 都允许为空,因为跨进程或受限平台存储项不保证提供本地路径和完整元数据。IUploadFileSource.OpenReadAsync 是 transport 读取内容的唯一稳定入口。
3.3 公共状态类型
public class UploadFileItem : AvaloniaObject
{
public Guid Id { get; set; }
public string? Name { get; set; }
public Uri? Path { get; set; }
public long Size { get; set; }
public FileUploadStatus Status { get; set; }
public double Progress { get; set; }
public string? ErrorMessage { get; set; }
public FileUploadResult? Result { get; set; }
public object? UserData { get; set; }
public string? PendingText { get; set; }
public bool IsImageFile { get; set; }
}
public enum UploadSourceKind
{
Files,
Directories
}
public enum UploadFileValueMode
{
AllFiles,
SuccessfulFiles,
Results
}
3.4 推荐用法
<atom:Upload Files="{Binding Attachments}"
UploadTransport="{Binding UploadTransport}"
ListMaxHeight="180"
ListScrollBarVisibility="Auto"
SuccessAutoRemoveDelay="0:0:3">
<atom:Upload.TriggerContent>
<StackPanel Orientation="Horizontal" Spacing="8">
<atom:UploadTrigger SourceKind="Files">
<atom:Button Content="上传文件" />
</atom:UploadTrigger>
<atom:UploadTrigger SourceKind="Directories">
<atom:Button Content="上传文件夹" />
</atom:UploadTrigger>
</StackPanel>
</atom:Upload.TriggerContent>
</atom:Upload>
4. 行为与状态模型
Upload 的状态流只允许按以下路径收敛:
Public API / UploadTrigger / UploadDropZone
-> UploadInputPipeline
-> top-level input limit / directory traversal / file admission
-> UI count policy / accepted file commit
-> Files collection
-> UploadQueue / FileUploadScheduler
-> UploadFileItem.Status / Progress / Result
-> UploadList item containers
-> Gallery 可观察行为
状态维护规则:
Files是唯一文件状态 owner;实现中按文件 id 保存的 acceptedUploadFileInfo只承担内容源 lease,不形成第二份可观察任务状态。UploadQueue只负责把UploadFileItem映射到FileUploadTask并转发调度结果,不直接操作视觉容器。UploadList只渲染Files,不得创建、删除或隐藏真实任务状态。- 文件选择和目录选择由
UploadTrigger.SourceKind决定,可以在同一Upload下并存。 IsMultipleEnabled是 picker 与 Drop 共享的顶层输入策略,不得复用为目录展开数量或最终文件容量限制。SuccessAutoRemoveDelay的延迟任务必须在 remove、reset、detach 和状态离开 success 时取消。- Form 值由
FileValueMode投影,错误状态以 AvaloniaDataValidationErrors为准。
5. 视觉与主题模型
Upload 的视觉模型由控件模板、ControlTheme、SharedToken 和控件 Token 共同构成。
| 主题文件 | 职责 |
|---|---|
UploadTheme.axaml |
根模板,连接 TriggerContent、list 和 picture display source。 |
UploadTriggerTheme.axaml |
触发器 shell,只承载用户内容和点击动作,不硬编码 Button。 |
UploadDropZoneTheme.axaml |
拖拽行为 shell,只承载用户内容和内容对齐。 |
UploadDefaultDropAreaTheme.axaml |
默认拖动视觉,保留边框、图标、标题、副标题、状态 selector 和动效,不处理拖动数据。 |
UploadListTheme.axaml |
上传列表 shell,内部拥有自动隐藏的 atom:ScrollViewer 和滚动边界。 |
UploadTextListItemTheme.axaml |
Text 列表项状态视觉。 |
UploadTextListItemHeaderTheme.axaml |
Text 列表项头部状态视觉。 |
UploadPictureListItemTheme.axaml |
Picture 列表项状态视觉。 |
UploadPicturePendingContentTheme.axaml |
Picture pending 内容,优先显示 UploadFileItem.PendingText。 |
UploadPicturePreviewContentTheme.axaml |
Picture preview 内容。 |
UploadPictureUploadingContentTheme.axaml |
Picture uploading 内容。 |
UploadPictureDefaultContentTheme.axaml |
Picture fallback 内容。 |
UploadPictureShapeListTheme.axaml |
PictureCard/PictureCircle 列表布局。 |
UploadPictureShapeListItemTheme.axaml |
PictureCard/PictureCircle 列表项状态视觉。 |
UploadPictureShapePendingContentTheme.axaml |
Shape pending 内容,优先显示 UploadFileItem.PendingText。 |
UploadPictureShapePreviewContentTheme.axaml |
Shape preview 内容。 |
UploadPictureShapeUploadingContentTheme.axaml |
Shape uploading 内容。 |
UploadPictureShapeDefaultContentTheme.axaml |
Shape fallback 内容。 |
主题维护规则:
- Trigger 不作为文件项渲染,PictureCard/PictureCircle 使用不进入
Files的 display append slot 保持同一 wrap flow。 - Text/Picture 根模板必须在 trigger 与 list 之间保留 Shared spacing,避免触发按钮和第一条文件项贴在一起。
- 滚动区域只包裹列表,不包裹 trigger。
- 可由 AXAML 表达的模板状态必须优先留在 AXAML。
- Token 只表达视觉变量,不承载上传状态、队列状态或 Form 错误。
6. 控件家族或集成关系
主要协作类型:
Upload:上传协调器,持有 public API、Files、上传队列、Form 适配和生命周期释放。UploadFileItem:文件状态模型,承载文件元数据、上传状态、进度、错误、结果和自定义 pending 文案。UploadQueue:内部上传队列协调器,映射UploadFileItem与FileUploadTask。UploadTrigger:独立触发控件,按UploadSourceKind调用最近Upload的文件或目录选择方法。UploadDropZone:独立拖拽控件,拥有 DragDrop 状态机、Copy/None 协商、Drop 快照和输入批次入口。UploadDefaultDropArea:默认拖动视觉控件,只渲染 DropZone 内容,不拥有 DataTransfer 或上传状态。UploadList:列表视图控件,渲染Files并维护滚动边界。AbstractUploadListItem:列表项基类,只绑定UploadFileItem属性并发出 item action。AbstractUploadPictureContent:图片类内容基类,投射UploadFileItem的视觉状态。FileUploadScheduler:共享上传调度器,必须正确跟踪 pending/running/completed/cancelled 状态。IFileUploadTransport:业务上传传输边界。
集成关系:
- Gallery 展示新的组合用法,不再示例
DefaultTaskList、IsUploadDirectoryEnabled或IsShowUploadTrigger。 - Form 读取
FileValueMode投影结果;Form 写入应更新Files内容而不是替换集合实例。 DataValidationErrors是 error 状态来源,Upload 不独立维护另一套 error 机制。
7. 兼容性不变量
维护 Upload 时必须保持以下兼容性不变量:
- 不引入与
Files平行的任务集合或把触发入口伪装成文件项。 - 不让视觉容器反向持有业务任务状态。
- 不用延时、强制刷新或 suppression flag 掩盖状态不同步。
- Template reapply、集合替换、remove、reset、detach 都必须释放旧订阅、取消运行任务和取消 pending auto-remove。
- 不通过运行时反射扫描 public API、Token 或 Gallery 示例数据。
UploadDropZone和UploadDefaultDropArea的 ControlTheme、模板视觉树、Token 映射、布局和默认渲染结果保持稳定。UploadDropZone的新增拖动状态伪类只提供自定义主题入口;AtomUI 默认主题不得据此改变 pointerover、disabled、motion、Light/Dark 或缩放后的视觉结果。- 文档只描述稳定设计;历史变化记录在
changelog.md。
8. 专项模型
8.1 文件选择模型
UploadTrigger.SourceKind 决定点击后的选择动作:
Files:打开文件选择器,支持AllowedFileTypes和MaxCount。Directories:打开目录选择器,并以TopLevelFiles目录策略提交统一输入管线。
文件和目录触发器可以同时存在于一个 Upload.TriggerContent 中。两种 picker 都从所属 Upload.IsMultipleEnabled 取得同一个 AllowMultiple 值,避免点击 DropZone、独立 UploadTrigger 和拖动输入形成不同的多选语义。
8.2 拖动输入模型
UploadDropZone 只在 Drop 阶段取得一次 StorageItem 快照;DragEnter 和 DragOver 仅检查 typed DataTransfer 格式并明确返回 Copy 或 None。顶层多选限制在目录展开前执行,随后目录展开、文件类型、业务准入、最终数量限制和资源释放全部收敛到 Upload 的统一输入管线。批次分别持有 typed StorageItem 和 source lease ownership,storage-to-source 与 source-to-Upload transfer 都验证当前 owner;单个释放异常不能截断其他资源清理。
UploadDefaultDropArea 保留当前默认渲染,但不再设置 DragDrop.AllowDrop、注册 Drop handler 或暴露第二条文件事件。完整状态、平台、Template 和生命周期契约见 Upload 拖动上传设计。
默认契约为 IsOpenFileDialogOnClick=true、SourceKind=Files、IsFileDropEnabled=true、DirectoryDropMode=Reject、MaxDirectoryDepth=32 和 MaxEnumeratedItems=10000。DragState=None 与 IsDropProcessing=false 只表达运行状态,默认主题不使用它们改变渲染。
8.3 列表滚动模型
UploadList 内部拥有滚动边界:
ListMaxHeight控制列表最大高度。ListScrollBarVisibility控制垂直滚动条。- 内部滚动容器应使用 AtomUI
ScrollViewer的 lite/auto-hide 模式。 - trigger 位于滚动区域外,用户滚动列表时上传按钮不移动。
8.4 自动移除模型
SuccessAutoRemoveDelay 不为空时,文件进入 success 后开始延迟移除。延迟任务由 Upload 统一管理,并在以下场景取消:
- 用户手动移除文件。
- 文件状态离开 success。
ResetAsync。- 控件 detach。
8.5 Form 与验证模型
FileValueMode 决定 Form 值:
AllFiles:返回全部UploadFileItem。SuccessfulFiles:返回成功文件。Results:返回成功文件的FileUploadResult。
错误状态必须投射到 Avalonia DataValidationErrors,不得另建 Upload 专属 error 机制。
9. 文档导航、LLMS 导出与验证策略
关联文档:
LLMS 语义区域:
| Part | AtomUI 节点 | 职责 | 相关 API | 相关 Token | 稳定性 |
|---|---|---|---|---|---|
root |
Upload |
上传状态协调器,拥有文件集合、用户输入范围、上传队列、Form 值投影和生命周期。 | Files、IsMultipleEnabled、UploadTransport、FileValueMode |
UploadToken、SharedToken |
stable |
trigger |
TriggerContent / UploadTrigger |
承载文件或目录选择入口,只提交选择动作,不持有上传状态。 | TriggerContent、SourceKind、SelectFilesAsync()、SelectDirectoriesAsync() |
Upload trigger 主题资源 | stable |
drop-zone |
UploadDropZone |
协商拖动效果、取得 Drop 快照并创建统一输入批次。 | IsOpenFileDialogOnClick、DirectoryDropMode、DragState |
Upload drop-zone 主题资源 | stable |
drop-area |
UploadDefaultDropArea |
渲染默认拖动图标、标题、副标题和边框,不处理 DataTransfer。 | DropIcon、Header、SubHeader |
Upload Token、SharedToken | stable |
list |
UploadList |
渲染 Files 并拥有列表滚动边界,不创建第二份文件状态。 |
Files、ListType、ListMaxHeight、ListScrollBarVisibility |
Upload list 主题资源 | internal-observable |
item |
AbstractUploadListItem 派生容器 |
投射单个 UploadFileItem 的状态、进度和操作入口。 |
UploadFileItem.Status、Progress、ErrorMessage、Result |
Upload item 主题资源 | internal-observable |
validation |
Upload Form / validation 投影 |
按 FileValueMode 输出 Form 值,并把错误投射到 DataValidationErrors。 |
FileValueMode、IFormItemAware |
SharedToken、Form Token | stable |
LLMS 导出来源:
| LLMS 内容 | 来源 | 说明 |
|---|---|---|
| 单控件完整文档 | overview.md + implementation.md + token.md + Gallery ShowCase |
生成 controls/upload/index-cn.md |
| 单控件语义文档 | overview.md + implementation.md + theme/template 信息 |
生成 controls/upload/semantic-cn.md |
| API 表 | overview.md 语义摘要 + 源码 public surface | 不在 overview.md 中复制完整 API 表 |
| Design Token 表 | token.md、Token 类型或第 5 节主题模型 | 不在生成产物中手工维护第二份 Token 表 |
| 示例 | Gallery ShowCase + source snippet catalog | 只引用稳定示例 |
| 源码索引 | implementation.md |
用于定位控件源码、主题和测试 |
验证策略:
| 改动类型 | 验证要求 |
|---|---|
| 文档改动 | 运行 git diff --check,检查相对链接存在。 |
| Public API | 覆盖属性默认值、事件触发、命令和绑定语义。 |
| 状态模型 | 覆盖 add、remove、reset、detach、collection replacement、cancel 和 auto-remove。 |
| AXAML/Theme | 检查 trigger 固定、list 内部滚动、display append slot、template part、资源 key 和 Light/Dark 主题。 |
| Token | 检查 TokenKind、AXAML token resource、Token 类型、生成数据和 token.md和文档同步。 |
| Gallery | 走查 fixed trigger、scrollable list、file/directory dual trigger 和 auto-remove 示例。 |