引言
在前端展示层面,鸿蒙壁纸大师充分利用了鸿蒙 A2A 协议的卡片能力,通过DisplayFaCard实现丰富的交互式 UI,而非简单的文本对话。整个壁纸创作流程中,用户看到的是可点击的选择列表、生动的进度提示和精美的壁纸展示卡片。
本文将深入解析前端的卡片交互设计,涵盖selectList选择卡片、wallpaper展示卡片、SSE 事件驱动的前端更新以及内容安全策略。
上图展示了鸿蒙壁纸大师中的两种核心卡片类型:selectList 选择卡片和 wallpaper 壁纸展示卡片,以及它们在交互流程中的切换关系
一、卡片体系总览
1.1 三种卡片类型
鸿蒙壁纸大师使用了三种卡片类型:
| 卡片类型 | 用途 | 展示阶段 |
|---|---|---|
| selectList | 交互式选择列表 | 风格选择、主题选择、场景选择 |
| wallpaper | 壁纸结果展示 | 壁纸生成完成 |
| text | 文本消息 | 闲聊、进度提示 |
核心概念:
DisplayFaCard是鸿蒙 A2A 协议中卡片展示的统一入口,通过cardName字段区分不同卡片类型,前端根据cardName渲染对应的 UI 组件。
1.2 卡片 JSON 结构规范
所有卡片都通过 A2A 协议的artifact-update事件的parts字段传递:
{"jsonrpc":"2.0","id":"request_id","result":{"taskId":"task_id","kind":"artifact-update","append":false,"lastChunk":true,"final":true,"artifact":{"artifactId":"task_id-options","parts":[{"kind":"data","data":{"cardsInfo":[{"cardName":"selectList","cardData":{...},"displayType":"DisplayFaCard"}]}}]}},"error":{"code":0,"message":"success"}}二、selectList 选择卡片
2.1 卡片构建函数
# agent_routes.pydefbuild_style_options_card(title:str,options:List[str],task_id:str,request_id:str,has_next_batch:bool=True,can_replay:bool=True,)->dict:"""构建风格/主题选项的卡片 SSE 事件"""items=[{"text":opt,"value":opt}foroptinoptions]return{"jsonrpc":"2.0","id":request_id,"result":{"taskId":task_id,"kind":"artifact-update","append":False,"lastChunk":True,"final":True,"artifact":{"artifactId":f"{task_id}-options","parts":[{"kind":"data","data":{"cardsInfo":[{"cardName":"selectList","cardData":{"title":title,"items":items,"canReplay":can_replay,},"displayType":"DisplayFaCard"}]}}]}},"error":{"code":0,"message":"success"}}2.2 三级选择流程的卡片展示
第一级:风格选择
{"cardName":"selectList","cardData":{"title":"你可以从下面挑选喜欢的风格,也可以直接告诉我你想要的其他风格。","items":[{"text":"极简主义","value":"极简主义"},{"text":"弥散风","value":"弥散风"},{"text":"水墨国风","value":"水墨国风"},{"text":"莫兰迪风","value":"莫兰迪风"}],"canReplay":true}}第二级:主题选择
{"cardName":"selectList","cardData":{"title":"选一个喜欢的主题,打造你的专属壁纸。","items":[{"text":"青峦远山","value":"青峦远山"},{"text":"寒梅疏枝","value":"寒梅疏枝"},{"text":"江上孤舟","value":"江上孤舟"},{"text":"竹林清风","value":"竹林清风"}],"canReplay":true}}第三级:场景选择
{"cardName":"selectList","cardData":{"title":"最后,选择你的壁纸尺寸和使用场景。","items":[{"text":"竖屏 · 锁屏","value":"竖屏 · 锁屏"},{"text":"竖屏 · 主屏","value":"竖屏 · 主屏"},{"text":"横屏 · 锁屏","value":"横屏 · 锁屏"},{"text":"横屏 · 主屏","value":"横屏 · 主屏"}],"canReplay":false}}2.3 卡片交互的用户体验
| 阶段 | 卡片标题 | 选项数 | 支持换一批 | 支持重选 |
|---|---|---|---|---|
| 选择风格 | 引导选择风格 | 4 | ✅ | ✅ |
| 选择主题 | 引导选择主题 | 4 | ✅ | ✅ |
| 选择场景 | 引导选择场景 | 4 | ❌ | ❌ |
交互流程:
用户看到 selectList 卡片 ↓ 点击某个选项(如"水墨国风") ↓ 前端将选项文本作为用户消息发送 ↓ 服务端识别关键词,跳过 LLM 意图识别 ↓ 构建下一级 selectList 卡片并推送 ↓ 前端自动刷新卡片2.4 快捷命令与卡片的联动
当用户输入"换一批"时,服务端重新生成当前级别的选项并重新推送卡片:
# agent_routes.py - 换一批处理if"换一批"inuser_text:ifflow_manager.current_step==FlowStep.SELECT_STYLE:flow_result=flow_manager.handle_style_batch_refresh()elifflow_manager.current_step==FlowStep.SELECT_THEME:flow_result=awaitflow_manager.handle_theme_batch_refresh()# 重新构建并推送 selectList 卡片batch_event=build_style_options_card(title=flow_result["guidance"],options=flow_result["options"],# 新的随机选项task_id=task_id,request_id=request_id,has_next_batch=True,can_replay=True,)yieldf"data:{json.dumps(batch_event,ensure_ascii=False)}\n\n"三、wallpaper 展示卡片
3.1 壁纸结果卡片
壁纸生成完成后,服务端推送wallpaper卡片展示结果:
# services/wallpaper_service.py# 步骤 5: 发送完成状态(卡片格式)text_event={"jsonrpc":"2.0","id":task_id,"result":{"taskId":task_id,"kind":"artifact-update","append":False,"lastChunk":True,"final":True,"artifact":{"artifactId":f"{task_id}-complete","parts":[{"kind":"data","data":{"cardsInfo":[{"cardName":"wallpaper","cardData":{"title":"壁纸生成完成","imageUrl":image_url,},"displayType":"DisplayFaCard"}]}}]}},"error":{"code":0,"message":"success"}}3.2 壁纸卡片的呈现
生成的 JSON 使前端呈现类似以下效果:
┌─────────────────────────┐ │ ✨ 壁纸生成完成 │ │ │ │ ┌───────────────┐ │ │ │ │ │ │ │ 壁纸图片 │ │ │ │ │ │ │ └───────────────┘ │ │ │ │ 风格:水墨国风 │ │ 主题:青峦远山 │ │ │ │ [换一张] [重新开始] │ └─────────────────────────┘3.3 与流程管理器的状态同步
# wallpaper_flow_service.pydefhandle_generate_complete(self,image_urls:List[str])->Dict[str,Any]:"""处理生成完成"""self.current_step=FlowStep.RESULT# 记录生成的壁纸forurlinimage_urls:self.generated_wallpapers.append({"url":url,"style":self.selected_style,"theme":self.selected_theme,})# 扣除免费次数ifself.free_credits_remaining>0:self.free_credits_remaining-=1return{"step":"result","message":"你的专属艺术壁纸已生成完成!","wallpapers":[{"url":url,"style":self.selected_style,"theme":self.selected_theme}forurlinimage_urls],"credits_remaining":self.free_credits_remaining,"has_regenerate":self.free_credits_remaining>0,}四、SSE 事件驱动的前端更新
4.1 完整事件序列
从前端视角看,一次完整的壁纸生成过程如下:
前端发送 message/stream 请求 ↓(后端返回 SSE 流) 事件1: status-update → state: submitted → "正在理解您的需求..." 事件2: status-update → state: working → "正在优化描述..." 事件3: status-update → state: working → "正在生成图片..." 事件4: status-update → state: working → "正在构思画面构图..." 事件5: status-update → state: working → "正在勾勒轮廓..." ... 事件N: artifact-update → final: true → wallpaper 卡片4.2 前端处理逻辑(示意)
// 前端 SSE 事件处理示意代码consteventSource=newEventSource('/agent/message');eventSource.onmessage=(event)=>{constdata=JSON.parse(event.data);constresult=data.result;switch(result.kind){case'status-update':// 更新进度状态updateProgress(result.status.message.parts[0].text);break;case'artifact-update':// 处理最终产物constcardsInfo=result.artifact.parts[0].data.cardsInfo;cardsInfo.forEach(card=>{switch(card.cardName){case'selectList':renderSelectListCard(card.cardData);break;case'wallpaper':renderWallpaperCard(card.cardData);break;}});break;}};4.3 卡片替换逻辑
每次推送新的selectList卡片时,前端需要替换上一张卡片:
append: false:替换上一张卡片(风格→主题→场景的步骤推进)append: true:追加到已有内容(闲聊的逐 token 输出)
风格选择卡片 (append: false) ↓ 用户选择风格 主题选择卡片 (append: false) → 替换风格卡片 ↓ 用户选择主题 场景选择卡片 (append: false) → 替换主题卡片 ↓ 用户选择场景 进度文本 (append: false) ↓ 逐条追加 进度文本 (append: true) ... ↓ 壁纸卡片 (append: false) → 替换所有内容五、内容安全策略
5.1 肖像检测
# wallpaper_flow_service.pydefcheck_content_policy(user_text:str)->Tuple[bool,Optional[str]]:"""检查内容是否符合规范(肖像、文字等限制)"""# 肖像检测关键词portrait_keywords=["我","自己","本人","照片","肖像","人脸","头像","明星","网红","模特","人物","人像","自拍",]# 文字/水印检测关键词text_keywords=["文字","字体","写字","名字","姓名","签名","水印","logo","LOGO","标志","品牌",]# 检测肖像请求forkeywordinportrait_keywords:ifkeywordinuser_text:ifany(phraseinuser_textforphrasein["像我","我的照片","加入","添加","包含"]):returnFalse,"抱歉呀,为了版权和隐私安全,我暂时不能生成包含人物肖像的壁纸哦~"# 检测文字/水印请求forkeywordintext_keywords:ifkeywordinuser_text:ifany(phraseinuser_textforphrasein["添加","写上","显示","包含","要有"]):returnFalse,"抱歉,目前暂时无法在壁纸上添加文字、水印或 LOGO~"returnTrue,None5.2 无关话题检测
# wallpaper_flow_service.pydefis_irrelevant_topic(user_text:str)->bool:"""判断是否是无关话题(闲聊、知识问答等)"""# 壁纸相关关键词wallpaper_keywords=["壁纸","背景","桌面","风格","主题","画面","图片","生成","创作","设计","图案","色彩","色调","锁屏","主屏","横屏","竖屏","尺寸","分辨率",]# 检查是否包含壁纸相关关键词forkeywordinwallpaper_keywords:ifkeywordinuser_text:returnFalse# 常见的无关问题类型irrelevant_patterns=["你是谁","你能做什么","天气","新闻","股票","帮我","写代码","翻译","计算",]forpatterninirrelevant_patterns:ifpatterninuser_text:returnTruereturnFalsedefget_irrelevant_response()->str:"""获取无关问题的统一回应"""return"我是你的专属壁纸创作助手,只专注于为你生成精美鸿蒙壁纸哦~我们继续设计你的专属壁纸吧!"5.3 安全策略在流程中的位置
# agent_routes.py - generate_sse() 中的安全检查# 1. 内容政策检查(肖像、文字等)policy_passed,policy_reject_reason=check_content_policy(user_text)ifnotpolicy_passed:# 返回拒绝卡片reject_event={..."text":policy_reject_reason}yieldf"data:{json.dumps(reject_event)}\n\n"return# 2. 无关话题检查(闲聊、知识问答等)ifis_irrelevant_topic(user_text):# 引导回壁纸主题irrelevant_event={..."text":get_irrelevant_response()}yieldf"data:{json.dumps(irrelevant_event)}\n\n"return# 3. 通过检查后,继续处理正常流程5.4 内容安全的交互设计
安全策略被触发时,用户看到的是友好的引导信息而非冷冰冰的错误提示:
| 违规类型 | 用户输入示例 | 系统回复 |
|---|---|---|
| 肖像请求 | “加入我的照片” | “为了版权和隐私安全,不能生成包含人物肖像的壁纸哦~” |
| 文字请求 | “写上’我爱你’” | “暂时无法在壁纸上添加文字、水印或 LOGO~” |
| 无关话题 | “帮我写代码” | “只专注于为你生成精美鸿蒙壁纸哦~” |
安全设计原则:内容安全策略应始终返回友好的引导信息而非冷冰冰的错误码,让用户感受到智能体的温度。同时,所有安全检查必须在进入主流程之前完成,避免浪费 API 调用资源。
六、卡片的扩展设计
6.1 卡片数据的可扩展性
当前cardData结构预留了扩展字段:
cardData={"title":"标题文本","items":[{"text":"选项","value":"value"}],# selectList"canReplay":True,# 支持重新选择# 可扩展字段"imageUrl":"...",# 壁纸展示卡片"style":"水墨国风",# 壁纸元信息"theme":"青峦远山","credits_remaining":5,# 剩余免费次数"has_regenerate":True,# 是否支持重新生成}6.2 未来可扩展的卡片类型
| 卡片类型 | 用途 | 状态 |
|---|---|---|
| imageGrid | 多张壁纸网格展示 | 未实现 |
| colorPicker | 颜色选择器 | 未实现 |
| progressBar | 自定义进度条 | 未实现 |
| shareCard | 壁纸分享卡片 | 未实现 |
七、常见问题与解决方案
在前端卡片开发中,常见问题及解决方案如下:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 卡片不显示 | cardName或displayType字段错误 | 检查 JSON 结构,确保使用DisplayFaCard |
| 卡片重叠 | append设置为true | 多级选择流程中使用false |
| 选项点击无响应 | 前端未正确处理点击事件 | 确保将选项文本作为用户消息发送 |
| SSE 连接断开 | 网络超时或服务端异常 | 前端实现自动重连机制 |
| 图片加载失败 | imageUrl无效或过期 | 检查图片 URL 的有效期 |
八、最佳实践总结
在鸿蒙壁纸大师的前端卡片交互开发中,以下最佳实践值得参考:
| 最佳实践 | 说明 | 收益 |
|---|---|---|
| 三级选择流程 | 风格→主题→场景的渐进式引导 | 降低用户决策成本 |
| 卡片替换机制 | 使用append: false替换上一张卡片 | 保持界面简洁 |
| SSE 事件驱动 | 实时推送进度和卡片更新 | 提升用户体验 |
| 内容安全前置 | 在主流程前完成安全检查 | 避免 API 资源浪费 |
| 友好引导设计 | 违规请求返回引导提示 | 提升用户满意度 |
| 卡片可扩展性 | 预留字段支持未来扩展 | 降低维护成本 |
开发建议:卡片交互设计应以用户为中心,每一步的选择都应有明确的引导和反馈。同时,合理使用
append字段控制卡片的替换与追加,避免界面混乱。
九、总结
本文深入解析了鸿蒙壁纸大师的前端卡片交互体系,核心要点:
- 三种卡片类型:
selectList(选择)、wallpaper(展示)、text(文本) - 三级选择流程:风格→主题→场景,每级通过卡片替换推进
- SSE 事件驱动:status-update 推送进度,artifact-update 推送卡片
- 内容安全策略:肖像检测、文字检测、无关话题过滤,确保合规
- 友好的交互设计:违规请求返回引导提示而非错误信息
- 卡片可扩展性:预留字段支持未来更多交互类型
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 华为 A2A DisplayFaCard 卡片规范
- 华为智能体交互设计指南
- A2A 协议规范 - 华为
- 华为开发者联盟 - 文档中心
- JSON-RPC 2.0 规范
- FastAPI 官方文档
- 火山引擎 - 豆包大模型
- Python asyncio 官方文档