{ "schemaVersion": 1, "sourceKind": "authored-spec", "provenance": "authored-in-repo", "specFile": "spec/移动端规格.md", "specSection": "46 · 上传 Upload", "confidence": "high", "slug": "mobile-upload", "name": "上传 Upload", "semanticTypeCandidates": [ "upload", "file-input", "attachment" ], "variantDimensions": [ { "name": "variant", "values": [ "dashed", "button" ] }, { "name": "maxCount", "values": [ "reached", "not-reached" ] } ], "representativeVariants": [ { "variant": "dashed", "maxCount": "not-reached", "label": "虚线框(页面级上传区)" }, { "variant": "button", "maxCount": "not-reached", "label": "实心按钮(列表内嵌的「+ 添加」)" }, { "variant": "dashed", "maxCount": "reached", "label": "到上限(触发器变成不可点的提示行)" }, { "variant": "dashed", "maxCount": "not-reached", "label": "禁用(触发器与删除按钮都不可用)" } ], "anatomy": { "upload": "根元素,纵向排列「标题行 + 触发器 + 文件列表」", "upload__header": "标题与计数(已选 N / 上限 M)", "upload__trigger": "选择触发器,点击调起系统文件选择器;dashed 是虚线框,button 是实心按钮", "upload__list": "文件列表容器", "upload__item": "单个文件行,行高不小于 44px", "upload__thumb": "缩略图位(图片用背景图,其他类型放扩展名文字块)", "upload__body": "文件名与「进度条 + 状态文案」的容器", "upload__track": "进度条轨道,role=progressbar", "upload__bar": "进度段,宽度取 --kole-m-upload-percent", "upload__actions": "行内动作(重试 / 删除),各自 44px 热区" }, "structurePatterns": { "variant": "dashed(虚线框,页面级上传区)/ button(实心按钮,列表内嵌的「+ 添加」)", "maxCount": "文件数上限(到达上限后触发器变成提示行,不再可点)", "disabled": "false(可交互)/ true(触发器与删除按钮都不可用)" }, "usageHints": [ "把手机里的文件交给服务端(实名认证的身份证照、报销的发票、工单的附件)", "移动端没有拖拽也没有 hover,触发按钮是唯一入口,虚线框只表达「这里可以放东西」", "点触发器调起系统文件选择器(本组件不发请求,选完由宿主拿文件并自行上传)", "进度不自己走:没有内置定时器或假进度,百分比全部由宿主回传", "删除按钮自己 44px 热区,点一次移除该项;删除进行中(uploading)时文案是「取消」", "失败行提供「重试」,重试由宿主重新发起,组件只回传下标" ], "doNotInvent": [ "真实的传输:请求、分片、断点续传、并发数(本组件只回传事件,不发任何请求)", "图片压缩、裁剪、水印与方向纠正", "服务端的校验规则(大小上限、类型白名单以文案与 accept 表达,不代为判断)", "拍照与相册的原生调起(由宿主在 select 事件里自行调用平台 API)" ], "unknowns": [ "上传中能否同时继续添加文件(当前允许,列表各自独立)", "失败自动重试的次数与退避策略", "是否需要在成功行上展示服务端返回的文件 id / URL" ], "interaction": [ "点触发器调起系统文件选择器(本组件不发请求,选完由宿主拿文件并自行上传)", "删除按钮自己 44px 热区,点一次移除该项;删除进行中(uploading)时文案是「取消」", "失败行提供「重试」,重试由宿主重新发起,组件只回传下标", "进度不自己走:没有内置定时器或假进度,百分比全部由宿主回传,避免出现「看起来在上传其实没动」", "列表为空时显示空态文案;到 maxCount 后触发器改成不可点的提示行" ], "accessibility": [ "触发器是原生 button,带 aria-disabled;到上限时用 disabled 让键盘跳过", "进度条写 role=\"progressbar\" + aria-valuemin / aria-valuemax / aria-valuenow,aria-label 说明是哪个文件的进度", "删除 / 重试按钮各自带 aria-label(含文件名),读屏播报「删除 合同扫描件.pdf」而不是孤零零的「删除」", "状态文案是文字而不只是颜色(「已上传」「上传失败」),颜色不作为唯一信息通道" ], "api": { "source": "implementation", "note": "props / events / slots 为 6 端实现的公共接口(说明文字取自规格对应小节)。字段名与各端源码逐名核对:node tools/verify-mobile-docs.mjs", "requiredNote": "「必传」按严格定义:实现里**没有默认值**时才为 Y(本门禁逐条核对 props 与各端源码的默认值,防止契约与实现脱节)。", "props": [ { "name": "files", "type": "Array<{ name: string, uid?: string, status?: 'pending'|'uploading'|'success'|'error', percent?: number }>", "default": "[]", "desc": "文件列表(宿主提供,状态与进度都由宿主回传)(规格 §46.4)", "required": false }, { "name": "maxCount", "type": "number", "default": "9", "desc": "文件数上限,到达后触发器变成提示行(规格 §46.3)", "required": false }, { "name": "disabled", "type": "boolean", "default": "false", "desc": "变体 disabled:触发器与删除按钮都不可用(规格 §46.3)", "required": false }, { "name": "variant", "type": "'dashed' | 'button'", "default": "'dashed'", "desc": "变体 variant:dashed 虚线框(页面级),button 实心按钮(列表内嵌)(规格 §46.3)", "required": false }, { "name": "title", "type": "string", "default": "'附件'", "desc": "标题行文案(规格 §46.2 upload__header)", "required": false }, { "name": "emptyText", "type": "string", "default": "'还没有上传文件'", "desc": "状态 empty 的占位文案(规格 §46.4)", "required": false }, { "name": "accept", "type": "string", "default": "''", "desc": "可接受的文件类型(透传给文件选择器;服务端校验规则不由本组件代为判断)(规格 §46.7)", "required": false } ], "events": [ { "name": "select", "params": "—", "desc": "点触发器时触发,宿主在此调起系统文件选择器(规格 §46.5)" }, { "name": "remove", "params": "index: number", "desc": "点删除(uploading 时是取消)时触发,回传条目下标(规格 §46.5)" }, { "name": "retry", "params": "index: number", "desc": "失败行点重试时触发,回传条目下标(规格 §46.5)" } ], "slots": [ { "name": "default", "desc": "自定义内容(追加在文件列表之后)" } ] }, "variantClasses": { "variant": { "dashed": [ ".kole-m-upload--dashed" ], "button": [ ".kole-m-upload--button" ] }, "maxCount": { "reached": [ ".kole-m-upload--full" ], "not-reached": [] } }, "demos": [ { "id": "trigger", "group": "01 组件类型", "title": "选择触发", "desc": "点一下调起系统文件选择器:移动端没有拖拽,这是唯一入口。", "variant": "variant=dashed" }, { "id": "states", "group": "01 组件类型", "title": "四种状态", "desc": "待上传 / 上传中(60%)/ 成功 / 失败;进度由宿主回传,组件不发请求。", "variant": "variant=button" }, { "id": "removable", "group": "02 组件状态", "title": "可删除", "desc": "删除按钮自己 44px 热区;删完后回到空态。", "variant": "状态 removable" }, { "id": "disabled", "group": "02 组件状态", "title": "禁用与到顶", "desc": "disabled 时触发器与删除都不可用;到 maxCount 后触发器改成提示行。", "variant": "状态 disabled | 变体 maxCount=reached" } ], "related": [ { "slug": "mobile-avatar", "why": "单张头像的更换是更窄的场景,用头像组件自带的编辑态,不需要整个上传列表" }, { "slug": "mobile-dialog", "why": "上传前要用户确认条款或选择来源时用它承载一步确认" }, { "slug": "mobile-list", "why": "只读的附件陈列(不可增删)用列表更轻,上传的列表带状态与动作" }, { "slug": "mobile-toast", "why": "上传成功 / 失败的单次反馈用它,不必在列表里堆第二条状态文案" }, { "slug": "mobile-loading", "why": "等待服务端返回文件地址这类短暂阻塞用它,进度有多少用上传自己的进度条" } ] }