1# 服务卡片开发指导(FA模型)
2
3
4## 卡片概述
5
6服务卡片(以下简称“卡片”)是一种界面展示形式,可以将应用的重要信息或操作前置到卡片,以达到服务直达、减少体验层级的目的。
7
8卡片常用于嵌入到其他应用(当前只支持系统应用)中作为其界面的一部分显示,并支持拉起页面、发送消息等基础的交互功能。
9
10卡片的基本概念:
11
12- 卡片使用方:显示卡片内容的宿主应用,控制卡片在宿主中展示的位置。
13
14- 卡片管理服务:用于管理系统中所添加卡片的常驻代理服务,包括卡片对象的管理与使用,以及卡片周期性刷新等。
15
16- 卡片提供方:提供卡片显示内容原子化服务,控制卡片的显示内容、控件布局以及控件点击事件。
17
18
19## 运作机制
20
21卡片框架的运作机制如图1所示。
22
23  **图1** 卡片框架运作机制(FA模型)
24![form-extension](figures/form-extension.png)
25
26卡片使用方包含以下模块:
27
28- 卡片使用:包含卡片的创建、删除、请求更新等操作。
29
30- 通信适配层:由OpenHarmony SDK提供,负责与卡片管理服务通信,用于将卡片的相关操作到卡片管理服务。
31
32卡片管理服务包含以下模块:
33
34- 周期性刷新:在卡片添加后,根据卡片的刷新策略启动定时任务周期性触发卡片的刷新。
35
36- 卡片缓存管理:在卡片添加到卡片管理服务后,对卡片的视图信息进行缓存,以便下次获取卡片时可以直接返回缓存数据,降低时延。
37
38- 卡片生命周期管理:对于卡片切换到后台或者被遮挡时,暂停卡片的刷新;以及卡片的升级/卸载场景下对卡片数据的更新和清理。
39
40- 卡片使用方对象管理:对卡片使用方的RPC对象进行管理,用于使用方请求进行校验以及对卡片更新后的回调处理。
41
42- 通信适配层:负责与卡片使用方和提供方进行RPC通信。
43
44卡片提供方包含以下模块:
45
46- 卡片服务:由卡片提供方开发者实现,开发者实现生命周期处理创建卡片、更新卡片以及删除卡片等请求,提供相应的卡片服务。
47
48- 卡片提供方实例管理模块:由卡片提供方开发者实现,负责对卡片管理服务分配的卡片实例进行持久化管理。
49
50- 通信适配层:由OpenHarmony SDK提供,负责与卡片管理服务通信,用于将卡片的更新数据主动推送到卡片管理服务。
51
52> **说明:**
53> 实际开发时只需要作为卡片提供方进行卡片内容的开发,卡片使用方和卡片管理服务由系统自动处理。
54
55
56## 接口说明
57
58FormAbility生命周期接口如下:
59
60| 接口名 | 描述 |
61| -------- | -------- |
62| onCreate(want: Want): formBindingData.FormBindingData | 卡片提供方接收创建卡片的通知接口。 |
63| onCastToNormal(formId: string): void | 卡片提供方接收临时卡片转常态卡片的通知接口。 |
64| onUpdate(formId: string): void | 卡片提供方接收更新卡片的通知接口。 |
65| onVisibilityChange(newStatus: Record<string, number>): void | 卡片提供方接收修改可见性的通知接口。 |
66| onEvent(formId: string, message: string): void | 卡片提供方接收处理卡片事件的通知接口。 |
67| onDestroy(formId: string): void | 卡片提供方接收销毁卡片的通知接口。 |
68| onAcquireFormState?(want: Want): formInfo.FormState | 卡片提供方接收查询卡片状态的通知接口。 |
69| onShare?(formId: string): {[key: string]: any} | 卡片提供方接收卡片分享的通知接口。 |
70| onShareForm?(formId: string): Record<string, Object> | 卡片提供方接收卡片分享的通知接口。推荐使用该接口替代onShare接口。如果了实现该接口,onShare将不再被回调。 |
71
72FormProvider类有如下API接口,具体的API介绍详见[接口文档](../reference/apis-form-kit/js-apis-app-form-formProvider.md)。
73
74
75| 接口名 | 描述 |
76| -------- | -------- |
77| setFormNextRefreshTime(formId: string, minute: number, callback: AsyncCallback<void>): void; | 设置指定卡片的下一次更新时间。 |
78| setFormNextRefreshTime(formId: string, minute: number): Promise<void>; | 设置指定卡片的下一次更新时间,以promise方式返回。 |
79| updateForm(formId: string, formBindingData: FormBindingData, callback: AsyncCallback<void>): void; | 更新指定的卡片。 |
80| updateForm(formId: string, formBindingData: FormBindingData): Promise<void>; | 更新指定的卡片,以promise方式返回。 |
81
82
83FormBindingData类有如下API接口,具体的API介绍详见[接口文档](../reference/apis-form-kit/js-apis-app-form-formBindingData.md)。
84
85
86| 接口名 | 描述 |
87| -------- | -------- |
88| createFormBindingData(obj?: Object \| string): FormBindingData | 创建一个FormBindingData对象。 |
89
90
91## 开发步骤
92
93FA卡片开发,即基于[FA模型](../application-models/fa-model-development-overview.md)的卡片提供方开发,主要涉及如下关键步骤:
94
95- [实现卡片生命周期接口](#实现卡片生命周期接口):开发FormAbility生命周期回调函数。
96
97- [配置卡片配置文件](#配置卡片配置文件):配置应用配置文件config.json98
99- [卡片信息的持久化](#卡片信息的持久化):对卡片信息进行持久化管理。
100
101- [卡片数据交互](#卡片数据交互):通过updateForm()更新卡片显示的信息。
102
103- [开发卡片页面](#开发卡片页面):使用HML+CSS+JSON开发JS卡片页面。
104
105- [开发卡片事件](#开发卡片事件):为卡片添加router事件和message事件。
106
107
108### 实现卡片生命周期接口
109
110创建FA模型的卡片,需实现卡片的生命周期接口。先参考<!--RP1-->[创建服务卡片](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-service-widget-V5)<!--RP1End-->生成服务卡片模板。
111
1121. 在form.ts中,导入相关模块
113
114    ```ts
115    import type featureAbility from '@ohos.ability.featureAbility';
116    import type Want from '@ohos.app.ability.Want';
117    import formBindingData from '@ohos.app.form.formBindingData';
118    import formInfo from '@ohos.app.form.formInfo';
119    import formProvider from '@ohos.app.form.formProvider';
120    import dataPreferences from '@ohos.data.preferences';
121    import hilog from '@ohos.hilog';
122    ```
123
1242. 在form.ts中,实现卡片生命周期接口
125
126    ```ts
127    const TAG: string = '[Sample_FAModelAbilityDevelop]';
128    const domain: number = 0xFF00;
129
130    const DATA_STORAGE_PATH: string = 'form_store';
131    let storeFormInfo = async (formId: string, formName: string, tempFlag: boolean, context: featureAbility.Context): Promise<void> => {
132      // 此处仅对卡片ID:formId,卡片名:formName和是否为临时卡片:tempFlag进行了持久化
133      let formInfo: Record<string, string | number | boolean> = {
134        'formName': 'formName',
135        'tempFlag': 'tempFlag',
136        'updateCount': 0
137      };
138      try {
139        const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH);
140        // put form info
141        await storage.put(formId, JSON.stringify(formInfo));
142        hilog.info(domain, TAG, `storeFormInfo, put form info successfully, formId: ${formId}`);
143        await storage.flush();
144      } catch (err) {
145        hilog.error(domain, TAG, `failed to storeFormInfo, err: ${JSON.stringify(err as Error)}`);
146      }
147    };
148
149    let deleteFormInfo = async (formId: string, context: featureAbility.Context) => {
150      try {
151        const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH);
152        // del form info
153        await storage.delete(formId);
154        hilog.info(domain, TAG, `deleteFormInfo, del form info successfully, formId: ${formId}`);
155        await storage.flush();
156      } catch (err) {
157        hilog.error(domain, TAG, `failed to deleteFormInfo, err: ${JSON.stringify(err)}`);
158      }
159    }
160
161    class LifeCycle {
162      onCreate: (want: Want) => formBindingData.FormBindingData = (want) => ({ data: '' });
163      onCastToNormal: (formId: string) => void = (formId) => {
164      };
165      onUpdate: (formId: string) => void = (formId) => {
166      };
167      onVisibilityChange: (newStatus: Record<string, number>) => void = (newStatus) => {
168        let obj: Record<string, number> = {
169          'test': 1
170        };
171        return obj;
172      };
173      onEvent: (formId: string, message: string) => void = (formId, message) => {
174      };
175      onDestroy: (formId: string) => void = (formId) => {
176      };
177      onAcquireFormState?: (want: Want) => formInfo.FormState = (want) => (0);
178      onShareForm?: (formId: string) => Record<string, Object> = (formId) => {
179        let obj: Record<string, number> = {
180          'test': 1
181        };
182        return obj;
183      };
184    }
185
186    let obj: LifeCycle = {
187      onCreate(want: Want) {
188        hilog.info(domain, TAG, 'FormAbility onCreate');
189        if (want.parameters) {
190          let formId = String(want.parameters['ohos.extra.param.key.form_identity']);
191          let formName = String(want.parameters['ohos.extra.param.key.form_name']);
192          let tempFlag = Boolean(want.parameters['ohos.extra.param.key.form_temporary']);
193          // 将创建的卡片信息持久化,以便在下次获取/更新该卡片实例时进行使用
194          // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
195          hilog.info(domain, TAG, 'FormAbility onCreate' + formId);
196          storeFormInfo(formId, formName, tempFlag, this.context);
197        }
198
199        // 使用方创建卡片时触发,提供方需要返回卡片数据绑定类
200        let obj: Record<string, string> = {
201          'title': 'titleOnCreate',
202          'detail': 'detailOnCreate'
203        };
204        let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
205        return formData;
206      },
207      onCastToNormal(formId: string) {
208        // 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
209        hilog.info(domain, TAG, 'FormAbility onCastToNormal');
210      },
211      onUpdate(formId: string) {
212        // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
213        hilog.info(domain, TAG, 'FormAbility onUpdate');
214        let obj: Record<string, string> = {
215          'title': 'titleOnUpdate',
216          'detail': 'detailOnUpdate'
217        };
218        let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
219        // 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变
220        formProvider.updateForm(formId, formData).catch((error: Error) => {
221          hilog.error(domain, TAG, 'FormAbility updateForm, error:' + JSON.stringify(error));
222        });
223      },
224      onVisibilityChange(newStatus: Record<string, number>) {
225        // 使用方发起可见或者不可见通知触发,提供方需要做相应的处理,仅系统应用生效
226        hilog.info(domain, TAG, 'FormAbility onVisibilityChange');
227      },
228      onEvent(formId: string, message: string) {
229        // 若卡片支持触发事件,则需要重写该方法并实现对事件的触发
230        let obj: Record<string, string> = {
231          'title': 'titleOnEvent',
232          'detail': 'detailOnEvent'
233        };
234        let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
235        // 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变
236        formProvider.updateForm(formId, formData).catch((error: Error) => {
237          hilog.error(domain, TAG, 'FormAbility updateForm, error:' + JSON.stringify(error));
238        });
239        hilog.info(domain, TAG, 'FormAbility onEvent');
240      },
241      onDestroy(formId: string) {
242        // 删除卡片实例数据
243        hilog.info(domain, TAG, 'FormAbility onDestroy');
244        // 删除之前持久化的卡片实例数据
245        // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
246        deleteFormInfo(formId, this.context);
247      },
248      onAcquireFormState(want: Want) {
249        hilog.info(domain, TAG, 'FormAbility onAcquireFormState');
250        return formInfo.FormState.READY;
251      }
252    };
253
254    export default obj;
255    ```
256
257> **说明:**
258> FormAbility不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务。
259
260### 配置卡片配置文件
261
262卡片需要在应用配置文件config.json中进行配置。
263
264- js模块,用于对应卡片的js相关资源,内部字段结构说明:
265    | 属性名称 | 含义 | 数据类型 | 是否可缺省 |
266  | -------- | -------- | -------- | -------- |
267  | name | 表示JS&nbsp;Component的名字。该标签不可缺省,默认值为default。 | 字符串 | 否 |
268  | pages | 表示JS&nbsp;Component的页面用于列举JS&nbsp;Component中每个页面的路由信息[页面路径+页面名称]。该标签不可缺省,取值为数组,数组第一个元素代表JS&nbsp;FA首页。 | 数组 | 否 |
269  | window | 用于定义与显示窗口相关的配置。 | 对象 | 可缺省。 |
270  | type | 表示JS应用的类型。取值范围如下:<br/>normal:标识该JS&nbsp;Component为应用实例。<br/>form:标识该JS&nbsp;Component为卡片实例。 | 字符串 | 可缺省,缺省值为“normal” 。|
271  | mode | 定义JS组件的开发模式。 | 对象 | 可缺省,缺省值为空。 |
272
273  配置示例如下:
274
275
276  ```json
277  "js": [
278    // ...
279    {
280      "name": "widget",
281      "pages": [
282        "pages/index/index"
283      ],
284      "window": {
285        "designWidth": 720,
286        "autoDesignWidth": true
287    	},
288        "type": "form"
289      }
290    ]
291  ```
292
293- abilities模块,用于对应卡片的FormAbility,内部字段结构说明:
294    | 属性名称 | 含义 | 数据类型 | 是否可缺省 |
295  | -------- | -------- | -------- | -------- |
296  | name | 表示卡片的类名。字符串最大长度为127字节。 | 字符串 | 否 |
297  | description | 表示卡片的描述。取值可以是描述性内容,也可以是对描述性内容的资源索引,以支持多语言。字符串最大长度为255字节。 | 字符串 | 可缺省,缺省为空。 |
298  | isDefault | 表示该卡片是否为默认卡片,每个Ability有且只有一个默认卡片。<br/>true:默认卡片。<br/>false:非默认卡片。 | 布尔值 | 否 |
299  | type | 表示卡片的类型。取值范围如下:<br/>JS:JS卡片。 | 字符串 | 否 |
300  | colorMode | 表示卡片的主题样式,取值范围如下:<br/>auto:自适应。<br/>dark:深色主题。<br/>light:浅色主题。 | 字符串 | 可缺省,缺省值为“auto”。 |
301  | supportDimensions | 表示卡片支持的外观规格,取值范围:<br/>1&nbsp;\*&nbsp;2:表示1行2列的二宫格。<br/>2&nbsp;\*&nbsp;2:表示2行2列的四宫格。<br/>2&nbsp;\*&nbsp;4:表示2行4列的八宫格。<br/>4&nbsp;\*&nbsp;4:表示4行4列的十六宫格。 | 字符串数组 | 否 |
302  | defaultDimension | 表示卡片的默认外观规格,取值必须在该卡片supportDimensions配置的列表中。 | 字符串 | 否 |
303  | updateEnabled | 表示卡片是否支持周期性刷新,取值范围:<br/>true:表示支持周期性刷新,可以在定时刷新(updateDuration)和定点刷新(scheduledUpdateTime)两种方式任选其一,优先选择定时刷新。<br/>false:表示不支持周期性刷新。 | 布尔类型 | 否 |
304  | scheduledUpdateTime | 表示卡片的定点刷新的时刻,采用24小时制,精确到分钟。<br/>updateDuration参数优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。 | 字符串 | 可缺省,缺省值为“0:0”。 |
305  | updateDuration | 表示卡片定时刷新的更新周期,单位为30分钟,取值为自然数。<br/>当取值为0时,表示该参数不生效。<br/>当取值为正整数N时,表示刷新周期为30\*N分钟。<br/>updateDuration参数优先级高于scheduledUpdateTime,两者同时配置时,以updateDuration配置的刷新时间为准。 | 数值 | 可缺省,缺省值为“0”。 |
306  | formConfigAbility | 表示卡片的配置跳转链接,采用URI格式。 | 字符串 | 可缺省,缺省值为空。 |
307  | formVisibleNotify | 标识是否允许卡片使用卡片可见性通知。 | 字符串 | 可缺省,缺省值为空。 |
308  | jsComponentName | 表示JS卡片的Component名称。字符串最大长度为127字节。 | 字符串 | 否 |
309  | metaData | 表示卡片的自定义信息,包含customizeData数组标签。 | 对象 | 可缺省,缺省值为空。 |
310  | customizeData | 表示自定义的卡片信息。 | 对象数组 | 可缺省,缺省值为空。 |
311
312  配置示例如下:
313
314
315  ```json
316  "abilities": [
317    // ...
318    {
319      "name": ".FormAbility",
320      "srcPath": "FormAbility",
321      "description": "$string:FormAbility_desc",
322      "icon": "$media:icon",
323      "label": "$string:FormAbility_label",
324      "type": "service",
325      "formsEnabled": true,
326      "srcLanguage": "ets",
327      "forms": [
328        {
329          "jsComponentName": "widget",
330          "isDefault": true,
331          "scheduledUpdateTime": "10:30",
332          "defaultDimension": "2*2",
333          "name": "widget",
334          "description": "This is a service widget.",
335          "colorMode": "auto",
336          "type": "JS",
337          "formVisibleNotify": true,
338          "supportDimensions": [
339            "2*2"
340          ],
341          "updateEnabled": true,
342          "updateDuration": 1
343        }
344      ]
345    },
346    // ...
347  ]
348  ```
349
350
351### 卡片信息的持久化
352
353因大部分卡片提供方都不是常驻服务,只有在需要使用时才会被拉起获取卡片信息,且卡片管理服务支持对卡片进行多实例管理,卡片ID对应实例ID,因此若卡片提供方支持对卡片数据进行配置,则需要对卡片的业务数据按照卡片ID进行持久化管理,以便在后续获取、更新以及拉起时能获取到正确的卡片业务数据。且需要适配onDestroy卡片删除通知接口,在其中实现卡片实例数据的删除。
354
355
356```ts
357const TAG: string = '[Sample_FAModelAbilityDevelop]';
358const domain: number = 0xFF00;
359
360const DATA_STORAGE_PATH: string = 'form_store';
361let storeFormInfo = async (formId: string, formName: string, tempFlag: boolean, context: featureAbility.Context): Promise<void> => {
362  // 此处仅对卡片ID:formId,卡片名:formName和是否为临时卡片:tempFlag进行了持久化
363  let formInfo: Record<string, string | number | boolean> = {
364    'formName': 'formName',
365    'tempFlag': 'tempFlag',
366    'updateCount': 0
367  };
368  try {
369    const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH);
370    // put form info
371    await storage.put(formId, JSON.stringify(formInfo));
372    hilog.info(domain, TAG, `storeFormInfo, put form info successfully, formId: ${formId}`);
373    await storage.flush();
374  } catch (err) {
375    hilog.error(domain, TAG, `failed to storeFormInfo, err: ${JSON.stringify(err as Error)}`);
376  }
377};
378
379let deleteFormInfo = async (formId: string, context: featureAbility.Context) => {
380  try {
381    const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH);
382    // del form info
383    await storage.delete(formId);
384    hilog.info(domain, TAG, `deleteFormInfo, del form info successfully, formId: ${formId}`);
385    await storage.flush();
386  } catch (err) {
387    hilog.error(domain, TAG, `failed to deleteFormInfo, err: ${JSON.stringify(err)}`);
388  }
389}
390
391// ...
392  onCreate(want: Want) {
393    hilog.info(domain, TAG, 'FormAbility onCreate');
394    if (want.parameters) {
395      let formId = String(want.parameters['ohos.extra.param.key.form_identity']);
396      let formName = String(want.parameters['ohos.extra.param.key.form_name']);
397      let tempFlag = Boolean(want.parameters['ohos.extra.param.key.form_temporary']);
398      // 将创建的卡片信息持久化,以便在下次获取/更新该卡片实例时进行使用
399      // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
400      hilog.info(domain, TAG, 'FormAbility onCreate' + formId);
401      storeFormInfo(formId, formName, tempFlag, this.context);
402    }
403
404    // 使用方创建卡片时触发,提供方需要返回卡片数据绑定类
405    let obj: Record<string, string> = {
406      'title': 'titleOnCreate',
407      'detail': 'detailOnCreate'
408    };
409    let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
410    return formData;
411  },
412// ...
413
414let deleteFormInfo = async (formId: string, context: featureAbility.Context): Promise<void> => {
415  try {
416    const storage = await dataPreferences.getPreferences(context, DATA_STORAGE_PATH);
417    // del form info
418    await storage.delete(formId);
419    hilog.info(domain, TAG, `deleteFormInfo, del form info successfully, formId: ${formId}`);
420    await storage.flush();
421  } catch (err) {
422    hilog.error(domain, TAG, `failed to deleteFormInfo, err: ${JSON.stringify(err)}`);
423  }
424};
425
426// ...
427    // 适配onDestroy卡片删除通知接口,在其中实现卡片实例数据的删除。
428  onDestroy(formId: string) {
429    // 删除卡片实例数据
430    hilog.info(domain, TAG, 'FormAbility onDestroy');
431    // 删除之前持久化的卡片实例数据
432    // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
433    deleteFormInfo(formId, this.context);
434  }
435// ...
436```
437
438具体的持久化方法可以参考[应用数据持久化概述](../database/app-data-persistence-overview.md)。
439
440需要注意的是,卡片使用方在请求卡片时传递给提供方应用的Want数据中存在临时标记字段,表示此次请求的卡片是否为临时卡片:
441
442- 常态卡片:卡片使用方会持久化的卡片。如添加到桌面的卡片。
443
444- 临时卡片:卡片使用方不会持久化的卡片。如上划卡片应用时显示的卡片。
445
446临时卡片转常态卡片:上划卡片应用后,此时会显示的卡片为临时卡片;点击卡片上的“图钉”按钮后添加到桌面,此时卡片转为常态卡片。
447
448由于临时卡片的数据具有非持久化的特殊性,某些场景例如卡片服务框架死亡重启,此时临时卡片数据在卡片管理服务中已经删除,且对应的卡片ID不会通知到提供方,所以卡片提供方需要自己负责清理长时间未删除的临时卡片数据。同时对应的卡片使用方可能会将之前请求的临时卡片转换为常态卡片。如果转换成功,卡片提供方也需要对对应的临时卡片ID进行处理,把卡片提供方记录的临时卡片数据转换为常态卡片数据,防止提供方在清理长时间未删除的临时卡片时,把已经转换为常态卡片的临时卡片信息删除,导致卡片信息丢失。
449
450
451### 卡片数据交互
452
453当卡片应用需要更新数据时(如触发了定时更新或定点更新),卡片应用获取最新数据,并调用updateForm()接口更新主动触发卡片的更新。
454
455
456```ts
457const TAG: string = '[Sample_FAModelAbilityDevelop]';
458const domain: number = 0xFF00;
459
460onUpdate(formId: string) {
461  // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
462  hilog.info(domain, TAG, 'FormAbility onUpdate');
463  let obj: Record<string, string> = {
464    'title': 'titleOnUpdate',
465    'detail': 'detailOnUpdate'
466  };
467  let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
468  // 调用updateForm接口去更新对应的卡片,仅更新入参中携带的数据信息,其他信息保持不变
469  formProvider.updateForm(formId, formData).catch((error: Error) => {
470    hilog.error(domain, TAG, 'FormAbility updateForm, error:' + JSON.stringify(error));
471  });
472}
473```
474
475
476### 开发卡片页面
477
478开发者可以使用类Web范式(HML+CSS+JSON)开发JS卡片页面。生成如下卡片页面,可以这样配置卡片页面文件:
479
480![widget-development-fa](figures/widget-development-fa.png)
481
482> **说明:**
483> FA模型当前仅支持JS扩展的类Web开发范式来实现卡片的UI。
484
485- HML:使用类Web范式的组件描述卡片的页面信息。
486
487  ```html
488  <div class="container">
489      <stack>
490          <div class="container-img">
491              <image src="/common/widget.png" class="bg-img"></image>
492              <image src="/common/rect.png" class="bottom-img"></image>
493          </div>
494          <div class="container-inner">
495              <text class="title" onclick="routerEvent">{{title}}</text>
496              <text class="detail_text" onclick="messageEvent">{{detail}}</text>
497          </div>
498      </stack>
499  </div>
500  ```
501
502- CSS:HML中类Web范式组件的样式信息。
503
504  ```css
505  .container {
506      flex-direction: column;
507      justify-content: center;
508      align-items: center;
509  }
510
511  .bg-img {
512      flex-shrink: 0;
513      height: 100%;
514      z-index: 1;
515  }
516
517  .bottom-img {
518      position: absolute;
519      width: 150px;
520      height: 56px;
521      top: 63%;
522      background-color: rgba(216, 216, 216, 0.15);
523      filter: blur(20px);
524      z-index: 2;
525  }
526
527  .container-inner {
528      flex-direction: column;
529      justify-content: flex-end;
530      align-items: flex-start;
531      height: 100%;
532      width: 100%;
533      padding: 12px;
534  }
535
536  .title {
537      font-family: HarmonyHeiTi-Medium;
538      font-size: 14px;
539      color: rgba(255,255,255,0.90);
540      letter-spacing: 0.6px;
541  }
542
543  .detail_text {
544      font-family: HarmonyHeiTi;
545      font-size: 12px;
546      color: rgba(255,255,255,0.60);
547      letter-spacing: 0.51px;
548      text-overflow: ellipsis;
549      max-lines: 1;
550      margin-top: 6px;
551  }
552  ```
553
554- JSON:卡片页面中的数据和事件交互。
555
556  ```json
557  {
558    "data": {
559      "title": "TitleDefault",
560      "detail": "TextDefault"
561    },
562    "actions": {
563      "routerEvent": {
564        "action": "router",
565        "abilityName": "com.samples.famodelabilitydevelop.MainAbility",
566        "params": {
567          "message": "add detail"
568        }
569      },
570      "messageEvent": {
571        "action": "message",
572        "params": {
573          "message": "add detail"
574        }
575      }
576    }
577  }
578  ```
579
580
581### 开发卡片事件
582
583卡片支持为组件设置交互事件(action),包括router事件和message事件,其中router事件用于Ability跳转,message事件用于卡片开发人员自定义点击事件。关键步骤说明如下:
584
5851. 在hml中为组件设置onclick属性,其值对应到json文件的actions字段中。
586
5872. 如何设置router事件:
588   - action属性值为"router";
589   - abilityName为跳转目标的Ability名(支持跳转FA模型的PageAbility组件和Stage模型的UIAbility组件),如目前DevEco创建的FA模型的UIAbility默认名为com.example.entry.EntryAbility590   - params为传递给跳转目标Ability的自定义参数,可以按需填写。其值可以在目标Ability启动时的want中的parameters里获取。如FA模型EntryAbility的onCreate生命周期里可以通过featureAbility.getWant()获取到want,然后在其parameters字段下获取到配置的参数;
591
5923. 如何设置message事件:
593   - action属性值为"message";
594   - params为message事件的用户自定义参数,可以按需填写。其值可以在卡片生命周期函数onEvent中的message里获取;
595
596示例如下:
597
598- hml文件
599
600  ```html
601  <div class="container">
602      <stack>
603          <div class="container-img">
604              <image src="/common/widget.png" class="bg-img"></image>
605              <image src="/common/rect.png" class="bottom-img"></image>
606          </div>
607          <div class="container-inner">
608              <text class="title" onclick="routerEvent">{{title}}</text>
609              <text class="detail_text" onclick="messageEvent">{{detail}}</text>
610          </div>
611      </stack>
612  </div>
613  ```
614
615- css文件
616
617  ```css
618  .container {
619      flex-direction: column;
620      justify-content: center;
621      align-items: center;
622  }
623
624  .bg-img {
625      flex-shrink: 0;
626      height: 100%;
627      z-index: 1;
628  }
629
630  .bottom-img {
631      position: absolute;
632      width: 150px;
633      height: 56px;
634      top: 63%;
635      background-color: rgba(216, 216, 216, 0.15);
636      filter: blur(20px);
637      z-index: 2;
638  }
639
640  .container-inner {
641      flex-direction: column;
642      justify-content: flex-end;
643      align-items: flex-start;
644      height: 100%;
645      width: 100%;
646      padding: 12px;
647  }
648
649  .title {
650      font-family: HarmonyHeiTi-Medium;
651      font-size: 14px;
652      color: rgba(255,255,255,0.90);
653      letter-spacing: 0.6px;
654  }
655
656  .detail_text {
657      font-family: HarmonyHeiTi;
658      font-size: 12px;
659      color: rgba(255,255,255,0.60);
660      letter-spacing: 0.51px;
661      text-overflow: ellipsis;
662      max-lines: 1;
663      margin-top: 6px;
664  }
665  ```
666
667- json文件
668
669  ```json
670  {
671    "data": {
672      "title": "TitleDefault",
673      "detail": "TextDefault"
674    },
675    "actions": {
676      "routerEvent": {
677        "action": "router",
678        "abilityName": "com.samples.famodelabilitydevelop.MainAbility",
679        "params": {
680          "message": "add detail"
681        }
682      },
683      "messageEvent": {
684        "action": "message",
685        "params": {
686          "message": "add detail"
687        }
688      }
689    }
690  }
691  ```
692