☰
【化学实验室|12】HarmonyOS ArkTS 桌面学习卡片实战:用 FormExtensionAbility 展示每日学习入口
2026/10/20 1:50:55 网站建设 项目流程

桌面学习卡片的价值不是把应用首页缩小后放到桌面,而是把“今天学什么”和“点一下去哪里”前置。用户不需要先打开应用、切换学习标签、找到每日一题,再判断是否完成;卡片应该直接给出当日任务,并把一次点击送到对应页面。

本文基于化学实验室 1.0.5的真实 HarmonyOS ArkTS 源码,拆解DailyStudyFormAbility.ets、DailyStudyCard.ets、form_config.json、module.json5和EntryAbility.ets。当前工程已经声明FormExtensionAbility,支持2*2与2*4,配置了 08:00 刷新,并用FormLink携带source: 'daily_study_card'拉起EntryAbility。表面链路完整,继续沿数据和路由逐步检查,却能发现三个关键断点。

第一,扩展能力通过buildFormData()创建了标题、任务和提示,但卡片页面把同样的文案全部写死,没有读取绑定数据,因此onUpdateForm()即使成功也看不出任何变化。第二,更新回调每次返回的仍是同一组静态字符串,08:00 或周期更新都不会产生“今日”差异。第三,FormLink传入的source没有在EntryAbility的冷启动或热启动生命周期中被消费,应用最终仍加载pages/Index的首页标签。

本文的核心判断是:桌面卡片不是一张会跳转的海报,而是一条由数据契约、刷新时机、卡片快照和启动路由共同组成的服务直达链路。只实现其中一个环节,用户就会看到“刷新了但没变化”或“点击了却还要自己找入口”。

本文唯一校验标记:CHEM-LAB-ONE12-DAILY-STUDY-FORM-CLOSURE-20260730

本文将完成这些工程问题的拆解:

  • 还原当前卡片从配置发现到系统更新的真实链路。
  • 让FormBindingData成为卡片页面的唯一数据来源。
  • 用日期键、任务 ID、完成态和目标路由构造稳定契约。
  • 设计冷启动与热启动都能消费的卡片跳转参数。
  • 区分2*2与2*4的信息密度,而不是简单拉伸。
  • 明确刷新策略、失败回退、实例清理和日志边界。
  • 给出可复现的真机测试矩阵与故障定位顺序。

一、先理解卡片提供方与卡片使用方

在当前场景中,化学实验室是卡片提供方,系统桌面是卡片使用方。应用负责声明卡片、生成数据、实现 UI 和处理交互;桌面负责添加、展示、删除和触发系统调度。

官方 Form Kit 术语将静态 ArkTS 卡片描述为适合 UI 相对固定、交互简单的场景:卡片渲染完成后,宿主使用最后一帧快照并释放运行资源。对于“每天展示一个学习任务、点击进入应用”的需求,这种形态是合理的,没有必要为了一个入口升级为长期运行的复杂动态卡片。

这里的“静态”不等于“永远不更新”。提供方仍可以在添加和更新回调中生成新的绑定数据,再由系统刷新卡片快照。

二、当前工程的四个声明位置

卡片能被系统发现,至少依赖四处一致配置:

  1. module.json5声明类型为form的DailyStudyFormAbility。
  2. 扩展能力通过metadata引用$profile:form_config。
  3. form_config.json声明DailyStudyCard的页面、尺寸和更新配置。
  4. src指向./ets/widget/pages/DailyStudyCard.ets。

当前module.json5的关键配置为:

"extensionAbilities": [ { "name": "DailyStudyFormAbility", "srcEntry": "./ets/formability/DailyStudyFormAbility.ets", "label": "$string:daily_study_form_label", "description": "$string:daily_study_form_desc", "type": "form", "metadata": [ { "name": "ohos.extension.form", "resource": "$profile:form_config" } ] } ]

这一层已经正确建立了“系统配置 -> 扩展能力 -> 卡片描述文件”的引用关系。后续排障时,只有系统完全看不到卡片,才优先检查这些声明;如果卡片能添加但内容不更新,应转向数据绑定链路。

三、form_config.json 决定系统怎样看待卡片

当前配置支持2*2和2*4,默认尺寸是2*2:

{ "name": "DailyStudyCard", "src": "./ets/widget/pages/DailyStudyCard.ets", "uiSyntax": "arkts", "colorMode": "auto", "updateEnabled": true, "scheduledUpdateTime": "08:00", "updateDuration": 1, "defaultDimension": "2*2", "supportDimensions": ["2*2", "2*4"] }

这里应重点验证三件事。第一,支持尺寸必须有真实布局适配。第二,colorMode: auto只有在页面使用可随模式变化的资源时才有意义。第三,定点和周期更新字段应根据项目目标 SDK 的官方说明选用并在真机验证,不应只因为模板生成了两个字段就同时保留。

每日学习任务通常不需要高频刷新。若内容只在日期变化或用户完成任务后改变,可以采用“每日定点 + 业务事件主动更新”的组合,避免无意义周期唤醒。

四、当前扩展能力的生命周期实现

DailyStudyFormAbility已覆盖创建、更新和删除:

export default class DailyStudyFormAbility extends FormExtensionAbility { onAddForm(want: Want): formBindingData.FormBindingData { return buildFormData() } onUpdateForm(formId: string): void { formProvider.updateForm(formId, buildFormData()).catch((err: Error) => { hilog.error(DOMAIN, 'DailyStudyForm', 'update form failed: %{public}s', err.message) }) } onRemoveForm(formId: string): void { hilog.info(DOMAIN, 'DailyStudyForm', 'onRemoveForm: %{public}s', formId) } }

生命周期框架本身没有缺失。真正的问题是buildFormData()返回什么、卡片页面是否读取它,以及点击后应用是否消费路由参数。

五、数据提供方与卡片 UI 目前各写了一份文案

扩展能力构造了:

interface DailyStudyFormData { title: string subtitle: string task: string tip: string }

但DailyStudyCard.ets又直接写了“今日化学学习”“完成一道化学题”等同样文本。两份内容暂时一样,容易让开发者误以为绑定成功;只要把task改成另一个值再更新,就会发现卡片没有变化。

这是一类典型的“假闭环”:提供方生成了数据,更新 API 也返回成功,但渲染页根本没有消费。判断刷新是否有效,必须以桌面实际快照变化为准,不能只看updateForm()Promise 成功。

六、把绑定字段接入 ArkTS 卡片页面

卡片页面应为绑定字段提供状态入口,默认值只用于数据尚未就绪时的可控展示:

@Entry @Component struct DailyStudyCard { @StorageProp('title') title: string = '今日化学学习' @StorageProp('subtitle') subtitle: string = '准备今天的学习任务' @StorageProp('taskTitle') taskTitle: string = '打开应用查看任务' @StorageProp('statusText') statusText: string = '待开始' @StorageProp('targetRoute') targetRoute: string = 'daily_quiz' build() { // Text(this.title)、Text(this.taskTitle) 等从绑定字段渲染 } }

字段名称必须与createFormBindingData()中的对象键完全一致。默认值不承担每日业务逻辑,只保证异常情况下卡片不出现空白。

七、重新定义每日学习卡片的数据契约

仅有四段展示文本不足以支持去重、更新、跳转和完成态。建议把数据契约扩展为:

export interface DailyStudyFormData { schemaVersion: number dateKey: string taskId: string taskType: 'quiz' | 'reading' | 'experiment_review' title: string subtitle: string taskTitle: string statusText: string completed: boolean targetRoute: string }

schemaVersion用于未来兼容字段变化,dateKey判断是否跨日,taskId保证内容身份稳定,taskType决定图标和目标页面,completed决定视觉状态。卡片 UI 不再根据中文文案猜业务状态。

八、数据生成不能散落在 FormExtensionAbility 中

扩展能力应管理生命周期,业务数据由仓库或服务提供:

export interface DailyStudyTask { id: string dateKey: string type: 'quiz' | 'reading' | 'experiment_review' title: string completed: boolean targetRoute: string } export class DailyStudyRepository { async getTask(dateKey: string): Promise<DailyStudyTask> { // 从项目现有本地数据服务读取,失败时返回受控默认任务 return this.buildFallbackTask(dateKey) } }

如果卡片数据来自本地持久化,仓库应使用扩展能力可用的上下文初始化存储。不要让卡片页面直接访问应用页面的内存状态,因为卡片渲染与应用 UI 不是同一个页面生命周期。

九、创建卡片时要记录实例维度

一个用户可能同时添加多个卡片实例,且尺寸不同。onAddForm(want)中的参数可以用于识别实例与维度;解析时应做类型保护,不要直接假设字段一定存在:

interface FormRequestInfo { formId: string dimension: number } function readFormRequest(want: Want): FormRequestInfo { const params = want.parameters const formId = String(params?.['ohos.extra.param.key.form_identity'] ?? '') const rawDimension = Number(params?.['ohos.extra.param.key.form_dimension'] ?? 0) return { formId, dimension: Number.isFinite(rawDimension) ? rawDimension : 0 } }

具体系统参数键和可用 API 应以项目目标 SDK 的 Form Kit 文档为准。示例的重点是:系统输入先规范化,再进入业务逻辑;日志和持久化只保存确实需要的字段。

十、不要把完整 want.parameters 作为公开日志

当前代码把JSON.stringify(want.parameters)以%{public}s输出。现阶段参数主要是卡片元数据,但未来一旦加入用户选择、任务 ID 或来源信息,完整公开日志会扩大泄露范围。

更稳的日志只记录排障所需字段:

hilog.info( DOMAIN, 'DailyStudyForm', 'add form, id=%{public}s, dimension=%{public}d', request.formId, request.dimension )

任务标题、学习记录和用户内容不应写入公开日志。错误日志记录错误码与受控消息,不输出整个对象。

十一、构造数据时要让日期和任务真正变化

可把“当前日期 -> 今日任务 -> 卡片契约”集中在一个构造器中:

function toFormData(task: DailyStudyTask): DailyStudyFormData { return { schemaVersion: 1, dateKey: task.dateKey, taskId: task.id, taskType: task.type, title: '今日化学学习', subtitle: task.completed ? '今日任务已完成' : '每天前进一小步', taskTitle: task.title, statusText: task.completed ? '已完成' : '待完成', completed: task.completed, targetRoute: task.targetRoute } }

同一天的同一个任务应生成稳定taskId,避免每次更新都变成新任务;跨日后dateKey和任务身份才更新。

十二、更新流程要有加载失败回退

onUpdateForm()返回void,内部可以启动异步更新,但必须在 Promise 链中收口错误:

onUpdateForm(formId: string): void { this.refreshForm(formId).catch((error: Error) => { hilog.error( DOMAIN, 'DailyStudyForm', 'refresh failed, id=%{public}s, reason=%{public}s', formId, error.message ) }) } private async refreshForm(formId: string): Promise<void> { const dateKey = this.clock.todayKey() const task = await this.repository.getTask(dateKey) const binding = formBindingData.createFormBindingData(toFormData(task)) await formProvider.updateForm(formId, binding) }

这里把时钟也抽成依赖,是为了在测试中固定日期,验证跨日更新而不等待第二天。

十三、每日卡片应该选择什么刷新策略

当前配置同时存在scheduledUpdateTime: '08:00'与updateDuration: 1。不同 API 版本对更新字段的约束和调度行为需要按目标 SDK 官方文档确认,工程上不应依赖未验证的组合。

对于化学学习卡片,可以定义清楚的业务策略:

  1. 每天早上刷新当天任务。
  2. 用户完成题目或阅读后主动更新完成态。
  3. 应用升级或数据版本变化时重建契约。
  4. 无需为了静态任务每隔短时间重复刷新。

系统调度不是精确定时器,测试时应允许延迟;业务正确性不能依赖“08:00 整秒一定回调”。

十四、完成任务后如何主动刷新卡片

当用户在应用内完成每日一题,业务层应先保存完成记录,再通知卡片更新。不要让答题页直接拼装卡片字段,可以调用统一服务:

export class DailyStudyCardUpdater { async refresh(formIds: string[]): Promise<void> { const task = await this.repository.getTask(this.clock.todayKey()) const data = formBindingData.createFormBindingData(toFormData(task)) for (const formId of formIds) { await formProvider.updateForm(formId, data) } } }

如果项目不需要保存实例 ID,就采用 Form Kit 针对当前版本提供的查询或更新方案;若保存实例 ID,必须在删除回调中清理失效记录。

十五、onRemoveForm 是否需要清理取决于是否保存实例状态

当前onRemoveForm()只记录日志。在完全无状态的卡片中,这足够;一旦为不同卡片保存尺寸、任务偏好或实例 ID,就必须删除对应数据:

onRemoveForm(formId: string): void { this.formInstanceRepository.remove(formId).catch((error: Error) => { hilog.error(DOMAIN, 'DailyStudyForm', 'remove instance failed: %{public}s', error.message) }) }

删除回调应幂等:记录已经不存在时也视为成功,避免系统重复通知造成异常。

十六、当前 FormLink 只证明能拉起应用

卡片页面把整个内容包在FormLink中:

FormLink({ action: 'router', abilityName: 'EntryAbility', params: { source: 'daily_study_card' } }) { // card content }

它能够携带来源参数拉起EntryAbility,但“拉起应用”和“直达学习任务”不是同一件事。当前EntryAbility.onCreate()没有读取want.parameters,也没有onNewWant();随后固定加载pages/Index。卡片点击因此只完成了链路前半段。

十七、为跳转定义稳定参数,不能只传 source

建议传递最小、可验证的启动契约:

export interface DailyStudyLaunchParams { source: 'daily_study_card' target: 'daily_quiz' | 'reading' | 'experiment_review' taskId: string dateKey: string }

source用于统计和回退,target决定页面,taskId定位内容,dateKey防止用户点击昨天的快照后误打开今天的另一条任务。卡片不传完整题目正文,避免参数过大和数据过期。

十八、冷启动与热启动必须分别处理

应用未运行时,参数从onCreate(want, ...)进入;应用已在前台或后台时,新的启动请求可能进入onNewWant(want, ...)。两条生命周期都应调用同一个规范化函数:

private acceptLaunchWant(want: Want): void { const launch = DailyStudyLaunchParser.parse(want.parameters) if (launch === undefined) { return } AppStorage.setOrCreate<DailyStudyLaunchParams>('pendingDailyLaunch', launch) } onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { this.acceptLaunchWant(want) // 其余初始化 } onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { this.acceptLaunchWant(want) }

具体生命周期签名以项目当前 SDK 类型定义为准。核心是冷、热启动共享解析逻辑,避免一种场景能直达、另一种只回首页。

十九、页面加载与数据初始化存在竞态

当前EntryAbility.onCreate()异步执行DataStore.init(),onWindowStageCreate()直接加载pages/Index。如果卡片目标页依赖存储,路由消费可能早于数据初始化完成。

可以让启动协调器保存两个条件:页面已就绪、数据已就绪。只有两者都满足时才消费pendingDailyLaunch:

interface StartupState { contentReady: boolean dataReady: boolean } private tryDispatchPendingLaunch(): void { if (!this.startupState.contentReady || !this.startupState.dataReady) return this.launchCoordinator.dispatchPending() }

如果任务读取失败,回退到学习首页并显示可恢复提示,不能留下白屏或无响应点击。

二十、Index 页需要接住学习标签与二级页面目标

当前Index.ets的currentIndex默认为 0,学习标签是索引 2。最小改造是先让启动协调器切换到学习标签,再由页面路由进入DailyQuizPage:

@StorageLink('requestedTabIndex') requestedTabIndex: number = 0 aboutToAppear(): void { this.currentIndex = this.requestedTabIndex this.tabController.changeIndex(this.requestedTabIndex) }

二级页面跳转应继续走项目已有路由方式。若路由失败或目标任务已过期,停留在学习标签,而不是返回空白页。

二十一、旧卡片快照必须有过期处理

用户可能在第二天 08:00 刷新前点击昨天卡片。应用收到dateKey后应与当前日期比较:

if (launch.dateKey !== this.clock.todayKey()) { return { target: 'learning_home', message: '卡片内容已更新,请查看今天的学习任务' } }

这比盲目使用taskId更安全。页面可以随后触发一次卡片刷新,让桌面快照尽快与当天内容一致。

二十二、2×2 与 2×4 不应该共用同一信息密度

当前配置声明两个尺寸,但只有一个卡片页面布局。它可以被拉伸,却没有利用2*4的额外空间。建议保持同一数据契约,按尺寸设计两种信息层级:

尺寸必须显示可选显示
2×2标题、今日任务、完成态、打开按钮一行简短副标题
2×42×2 全部内容学习连续天数、预计用时、下一任务

尺寸适配不是把字号放大。2×2 要保证最长任务名省略后仍能看懂,2×4 则利用宽度提供一个额外决策信息点。

二十三、卡片 UI 要从硬编码颜色迁移到资源

DailyStudyCard.ets直接使用#0052D9、#047481、#FFFFFF等颜色,同时配置了colorMode: auto。如果没有深浅色资源映射,“auto”不会自动让硬编码渐变和文字达到合理对比。

卡片资源至少应区分:背景起止色、主文字、次文字、状态标签、按钮背景和完成态。文字在渐变最亮和最暗位置都要可读;系统深色模式、不同壁纸和桌面缩放下都需要截图验证。

二十四、点击区域要明确且不制造误触

当前整个卡片都是FormLink,适合只有一个动作的静态入口。但卡片内部又绘制了“学习”和“打开”两个视觉按钮,用户可能误以为它们有不同功能。

如果卡片只有一个目标,保留整卡点击,同时把视觉按钮统一成一个明确动作;如果 2×4 未来出现多个入口,应分别定义交互区域,并确保参数目标不同。交互设计不能比实际能力更复杂。

二十五、卡片数据的空态、错误态和完成态

卡片没有普通页面那么多空间,但仍需要三类受控状态:

export type CardContentState = 'ready' | 'completed' | 'fallback'
  • ready:显示任务与“去完成”。
  • completed:显示已完成和次日提示,不重复催促。
  • fallback:数据读取失败时显示“打开应用继续学习”,仍保留安全入口。

不要在卡片上展示技术错误码。错误码进入日志,用户看到的是可执行的回退动作。

二十六、职责拆分后的整体结构

推荐的依赖方向是:

FormExtensionAbility -> DailyStudyCardService -> DailyStudyRepository -> FormDataMapper -> FormProviderAdapter DailyStudyCard UI -> FormBindingData fields -> FormLink launch contract EntryAbility -> LaunchParser -> StartupCoordinator -> Index / target page

扩展能力不拼业务文案,卡片 UI 不查持久化,入口 Ability 不理解题目数据。每一层只处理一种变化原因。

二十七、对构造器做纯函数测试

toFormData()不依赖系统上下文,最适合先写单元测试:

const task: DailyStudyTask = { id: 'quiz-20260717', dateKey: '2026-07-17', type: 'quiz', title: '完成一道酸碱平衡题', completed: false, targetRoute: 'daily_quiz' } const data = toFormData(task) expect(data.taskId).assertEqual('quiz-20260717') expect(data.statusText).assertEqual('待完成') expect(data.targetRoute).assertEqual('daily_quiz')

还要验证完成态、空标题回退、超长标题截断策略和未知任务类型。纯函数测试失败时,不必启动桌面卡片环境。

二十八、用适配器测试更新失败

直接调用系统formProvider不易制造异常,可以封装窄接口:

export interface FormUpdater { update(formId: string, data: DailyStudyFormData): Promise<void> } export class SystemFormUpdater implements FormUpdater { async update(formId: string, data: DailyStudyFormData): Promise<void> { const binding = formBindingData.createFormBindingData(data) await formProvider.updateForm(formId, binding) } }

测试替身可以模拟实例不存在、数据加载失败和连续两次更新。服务层应记录失败但不崩溃,也不能把一个实例失败误判成所有实例失败。

二十九、冷启动与热启动测试矩阵

场景前置状态预期结果
冷启动点击应用未运行初始化完成后进入每日一题
热启动点击应用停在实验室标签切换学习标签并进入目标页
旧日期点击卡片 dateKey 为昨天回退学习首页并提示内容更新
未知 target参数被篡改或版本过旧忽略目标,安全进入学习首页
数据初始化失败本地仓库不可用不白屏,展示可重试提示
重复点击连续点击同一卡片不叠加重复目标页

如果只验证冷启动,应用已在运行时的onNewWant漏洞很容易带到发布版本。

三十、卡片显示测试矩阵

检查项2×22×4
普通任务标题与动作完整增加辅助信息
超长任务单行省略且主动作可见最多两行,不挤压按钮
完成态明确“已完成”展示下一步建议
数据失败回退文案和入口回退文案和入口
深色模式文字、渐变、状态标签可读同左
大字体不重叠、不截断核心动作信息按优先级收缩

还要在手机、平板和 2in1 的桌面环境中验证实际卡片支持情况。应用声明支持这些设备,不代表桌面卡片布局自动适配完成。

三十一、常见故障一:系统完全找不到卡片

按下面顺序检查:

  1. module.json5是否声明type: 'form'。
  2. srcEntry路径与类文件是否一致。
  3. metadata 是否引用正确 profile。
  4. form_config.json的name、src和uiSyntax是否有效。
  5. 卡片页面是否成功编译进包。
  6. 安装的是不是包含最新配置的构建产物。

此时不要先改业务数据,因为系统尚未走到数据阶段。

三十二、常见故障二:updateForm 成功但界面不变

先把绑定字段临时改成可辨识值,例如标题追加日期。若桌面仍不变,检查:

  1. 卡片页面是否用@StorageProp或当前 SDK 对应绑定方式读取同名键。
  2. createFormBindingData()是否包含该字段。
  3. 更新的formId是否对应桌面上的实例。
  4. 桌面宿主是否已接收新快照。
  5. 是否实际上仍在看旧的另一个卡片实例。

当前项目首先应修复第 1 项,因为卡片页面仍使用硬编码文本。

三十三、常见故障三:点击卡片只回首页

确认FormLink的abilityName能匹配EntryAbility后,继续检查:

  1. onCreate是否读取启动参数。
  2. 热启动时是否实现onNewWant。
  3. 参数是否在页面加载前被覆盖。
  4. Index是否消费标签切换请求。
  5. 目标页路由是否在main_pages.json中存在。

当前DailyQuizPage已在main_pages.json中,主要缺口是参数接收和启动协调。

三十四、常见故障四:08:00 没有准时刷新

系统卡片更新受版本、调度策略、设备状态和配置约束影响,不应当作精确闹钟。排查时记录实际回调时间,确认updateEnabled与目标 SDK 配置有效,并在业务完成事件后主动更新。

若每日内容必须在首次查看时正确,卡片点击进入应用后还应重新校验当前日期,不能把正确性完全押在系统定点刷新上。

三十五、真机验收清单

  1. 安装新包后能在桌面卡片列表找到“今日化学学习”。
  2. 添加 2×2 与 2×4 两种实例,布局均无重叠。
  3. 首次添加时绑定数据与当日任务一致。
  4. 修改今日任务并触发更新后,桌面文案发生变化。
  5. 完成任务后卡片变成已完成态。
  6. 冷启动点击直达对应学习页。
  7. 应用已运行时再次点击仍能直达。
  8. 删除一个实例后,其实例记录被清理且其他实例不受影响。
  9. 重启设备后卡片不白屏,点击仍有安全回退。
  10. 浅色、深色、大字体和不同壁纸下均可读。

三十六、分阶段落地更容易定位问题

第一阶段只把FormBindingData接入卡片页面,用一个可辨识日期字段证明数据闭环。第二阶段接入每日任务仓库和完成态。第三阶段定义启动契约,分别打通冷、热启动。第四阶段完成 2×2、2×4 和主题适配。第五阶段补主动刷新、实例清理和真机回归。

每阶段只改变一个验证点:看到卡片变化、看到任务变化、看到页面直达、看到尺寸适配、看到完成态同步。这样一旦失败,可以立刻定位到对应层。

三十七、完成标准应该写成可观察结果

这张卡片的验收不是“代码里有FormExtensionAbility”,而是:

  • 系统能发现并添加卡片。
  • 卡片显示的是绑定数据,不是页面硬编码副本。
  • 日期或完成态变化后,桌面快照能被更新。
  • 点击参数在冷启动和热启动都被消费。
  • 目标无效或数据失败时,安全回退到学习首页。
  • 2×2 与 2×4 都提供清晰、不过载的信息。

三十八、官方资料与版本核对入口

本文结合项目真实源码与当前工程结构给出改造方案。Form Kit 配置字段、生命周期签名和交互能力会随 API 版本演进,落地时应以项目目标 SDK 的华为官方文档为准:

  • Form Kit 术语与卡片角色
  • Form Kit 产品与能力入口
  • HarmonyOS 开发文档中心

特别是更新策略、系统参数键和onNewWant的准确签名,应在 DevEco Studio 当前 SDK 类型提示中再次确认,并通过真机回调日志验证。

三十九、源码事实与建议实现的边界

为了避免把设计方案写成已经上线的能力,下面将当前仓库可以复核的 Form Kit 实现与建议演进明确分开。

观察维度当前源码事实建议实现
扩展能力DailyStudyFormAbility继承FormExtensionAbility,实现添加、更新和移除回调保留系统生命周期入口,把数据构建委托给可测试的每日任务服务
初始数据buildFormData()返回标题、副标题、任务和提示四个固定字符串使用日期键、任务 ID、完成态、目标路由和版本字段构建稳定契约
更新行为onUpdateForm调用formProvider.updateForm,但每次仍生成同一组静态字符串更新时读取真实任务状态;失败时保留最近有效快照并记录可定位日志
卡片 UIDailyStudyCard.ets中标题、副标题和任务文案全部硬编码从FormBindingData读取字段,避免提供方数据与 UI 副本分叉
点击行为FormLink使用router动作,目标为EntryAbility,参数只有source: daily_study_card增加受控目标路由与任务 ID,并对未知目标做白名单回退
冷启动EntryAbility.onCreate接收Want,但当前没有消费卡片source在初始化完成后解析启动契约,并把一次性导航意图交给页面路由层
热启动当前EntryAbility没有实现卡片参数的onNewWant消费路径按目标 SDK 的准确签名补热启动处理,避免应用已运行时点击失效
首屏加载onWindowStageCreate固定加载pages/Index继续加载稳定首屏,再由明确的导航状态直达目标或安全回退
尺寸与更新配置支持2*2、2*4,默认2*2,启用更新并配置 08:00 与周期参数为两个尺寸定义不同信息密度,并在目标 SDK 与真机上验证实际调度语义
主题策略卡片配置使用colorMode: auto,页面本身采用固定渐变与白色文字验证深浅色、壁纸、大字体与快照对比度,必要时提供资源化主题

本文提出的每日任务仓库、版本化DailyStudyFormData、日期键、完成态同步、尺寸分级布局、冷/热启动统一消费器和主动刷新策略,都是建议实现,当前源码尚未集成。本次精修没有执行 DevEco Studio 构建、卡片安装添加、08:00 调度、设备重启或真机点击直达测试,因此不能据此声称卡片数据闭环和服务直达已经修复;相关配置字段与生命周期签名还必须以项目目标 SDK 类型和真机日志为准。

总结

化学实验室现有桌面卡片已经完成了声明、扩展能力、卡片页面和FormLink的基础搭建,最值得修复的不是视觉细节,而是两条没有闭环的链路:FormBindingData没有进入 UI,FormLink参数没有进入业务页面。更新回调反复提交静态文案、两个尺寸共用同一信息密度,也让“每日学习入口”的产品价值没有真正体现。

把每日任务建模为带日期键、任务 ID、完成态和目标路由的稳定契约,再让 Form 扩展、卡片页面和启动路由共同消费这份契约,才能得到可信的服务直达体验。卡片显示什么、何时更新、点击去哪、失败如何回退,都必须能被真机观察和测试,而不是只在配置文件里看起来完整。

AI 辅助声明:本文由 AI 辅助完成源码梳理、结构校对与配图设计,技术结论均以文中列出的本地 ArkTS 源码为依据,并由作者复核后发布。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询