开发基于JS UI的卡片
以下内容介绍基于类Web范式的JS UI卡片开发指南。
运作机制
卡片框架的运作机制如图1所示。
图1 卡片框架运作机制(Stage模子)
卡片使用方包罗以下模块:
- 卡片使用:包罗卡片的创建、删除、请求更新等操纵。
- 通信适配层:由OpenHarmony SDK提供,负责与卡片管理服务通信,用于将卡片的干系操纵到卡片管理服务。
卡片管理服务包罗以下模块:
- 周期性刷新:在卡片添加后,根据卡片的刷新策略启动定时任务周期性触发卡片的刷新。
- 卡片缓存管理:在卡片添加到卡片管理服务后,对卡片的视图信息进行缓存,以便下次获取卡片时可以直接返回缓存数据,低落时延。
- 卡片生命周期管理:对于卡片切换到后台或者被遮挡时,停息卡片的刷新;以及卡片的升级/卸载场景下对卡片数据的更新和清理。
- 卡片使用方对象管理:对卡片使用方的RPC对象进行管理,用于使用方请求进行校验以及对卡片更新后的回调处理。
- 通信适配层:负责与卡片使用方和提供方进行RPC通信。
卡片提供方包罗以下模块:
- 卡片服务:由卡片提供方开发者实现,开发者实现生命周期处理创建卡片、更新卡片以及删除卡片等请求,提供相应的卡片服务。
- 卡片提供方实例管理模块:由卡片提供方开发者实现,负责对卡片管理服务分配的卡片实例进行持久化管理。
- 通信适配层:由OpenHarmony SDK提供,负责与卡片管理服务通信,用于将卡片的更新数据主动推送到卡片管理服务。
说明:
实际开发时只需要作为卡片提供方进行卡片内容的开发,卡片使用方和卡片管理服务由体系主动处理。
接口说明
FormExtensionAbility类拥有如下API接口。
接口名形貌onAddForm(want: Want): formBindingData.FormBindingData卡片提供方接收创建卡片的通知接口。onCastToNormalForm(formId: string): void卡片提供方接收临时卡片转常态卡片的通知接口。onUpdateForm(formId: string): void卡片提供方接收更新卡片的通知接口。onChangeFormVisibility(newStatus: Record<string, number>): void卡片提供方接收修改可见性的通知接口。onFormEvent(formId: string, message: string): void卡片提供方接收处理卡片事件的通知接口。onRemoveForm(formId: string): void卡片提供方接收销毁卡片的通知接口。onConfigurationUpdate(newConfig: Configuration): void当体系设置更新时调用。onShareForm?(formId: string): Record<string, Object>卡片提供方接收卡片分享的通知接口。 formProvider类有如下API接口
接口名形貌setFormNextRefreshTime(formId: string, minute: number, callback: AsyncCallback): void设置指定卡片的下一次更新时间。setFormNextRefreshTime(formId: string, minute: number): Promise设置指定卡片的下一次更新时间,以promise方式返回。updateForm(formId: string, formBindingData: formBindingData.FormBindingData, callback: AsyncCallback): void更新指定的卡片。updateForm(formId: string, formBindingData: formBindingData.FormBindingData): Promise更新指定的卡片,以promise方式返回。 接口名形貌createFormBindingData(obj?: Objectstring): FormBindingData 开发步骤
Stage卡片开发,即基于[Stage模子]的卡片提供方开发,主要涉及如下关键步骤:
- [创建卡片FormExtensionAbility]:卡片生命周期回调函数FormExtensionAbility开发。
- [设置卡片设置文件]:设置应用设置文件module.json5和profile设置文件。
- [卡片信息的持久化]:对卡片信息进行持久化管理。
- [卡片数据交互]:通过updateForm更新卡片表现的信息。
- [开发卡片页面]:使用HML+CSS+JSON开发JS卡片页面。
- [开发卡片事件]:为卡片添加router事件和message事件。
创建卡片FormExtensionAbility
创建Stage模子的卡片,需实现FormExtensionAbility生命周期接口。
- 在EntryFormAbility.ets中,导入干系模块。
- import { Want } from '@kit.AbilityKit';
- import { formBindingData, FormExtensionAbility, formInfo, formProvider } from '@kit.FormKit';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- const TAG: string = 'JsCardFormAbility';
- const DOMAIN_NUMBER: number = 0xFF00;
- ts
复制代码 - 在EntryFormAbility.ets中,实现FormExtension生命周期接口。
- export default class EntryFormAbility extends FormExtensionAbility {
- onAddForm(want: Want): formBindingData.FormBindingData {
- hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onAddForm');
- // 使用方创建卡片时触发,提供方需要返回卡片数据绑定类
- let obj: Record<string, string> = {
- 'title': 'titleOnCreate',
- 'detail': 'detailOnCreate'
- };
- let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
- return formData;
- }
- onCastToNormalForm(formId: string): void {
- // 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
- hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onCastToNormalForm');
- }
- onUpdateForm(formId: string): void {
- // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
- hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onUpdateForm');
- let obj: Record<string, string> = {
- 'title': 'titleOnUpdate',
- 'detail': 'detailOnUpdate'
- };
- let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
- formProvider.updateForm(formId, formData).catch((error: BusinessError) => {
- hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] updateForm, error:' + JSON.stringify(error));
- });
- }
- onChangeFormVisibility(newStatus: Record<string, number>): void {
- // 使用方发起可见或者不可见通知触发,提供方需要做相应的处理,仅系统应用生效
- hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onChangeFormVisibility');
- //...
- }
- onFormEvent(formId: string, message: string): void {
- // 若卡片支持触发事件,则需要重写该方法并实现对事件的触发
- hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onFormEvent');
- }
- onRemoveForm(formId: string): void {
- // 删除卡片实例数据
- hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onRemoveForm');
- //...
- }
- onAcquireFormState(want: Want): formInfo.FormState {
- return formInfo.FormState.READY;
- }
- }
- ts
复制代码 说明: FormExtensionAbility不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务。
设置卡片设置文件
- 卡片需要在[module.json5设置文件]中的extensionAbilities标签下,设置ExtensionAbility干系信息。FormExtensionAbility需要填写metadata元信息标签,其中键名称为固定字符串"ohos.extension.form",资源为卡片的详细设置信息的索引。 设置示例如下:
- {
- "module": {
- // ...
- "extensionAbilities": [
- {
- "name": "JsCardFormAbility",
- "srcEntry": "./ets/jscardformability/JsCardFormAbility.ts",
- "description": "$string:JSCardFormAbility_desc",
- "label": "$string:JSCardFormAbility_label",
- "type": "form",
- "metadata": [
- {
- "name": "ohos.extension.form",
- "resource": "$profile:form_jscard_config"
- }
- ]
- }
- ]
- }
- }
- json
复制代码 - 卡片的详细设置信息。在上述FormExtensionAbility的元信息("metadata"设置项)中,可以指定卡片详细设置信息的资源索引。例如当resource指定为$profile:form_config时,会使用开发视图的resources/base/profile/目录下的form_config.json作为卡片profile设置文件。内部字段布局说明如下表所示。
表1 卡片profile设置文件
属性名称寄义数据类型是否可缺省name表示卡片的类名,字符串最大长度为127字节。字符串否description表示卡片的形貌。取值可以是形貌性内容,也可以是对形貌性内容的资源索引,以支持多语言。字符串最大长度为255字节。字符串可缺省,缺省为空。src表示卡片对应的UI代码的完备路径。字符串否window用于定义与表现窗口干系的设置。对象可缺省。isDefault表示该卡片是否为默认卡片,每个UIAbility有且只有一个默认卡片。 - true:默认卡片。 - false:非默认卡片。布尔值否colorMode表示卡片的主题样式,取值范围如下: - auto:自顺应。 - dark:深色主题。 - light:浅色主题。字符串可缺省,缺省值为“auto”。supportDimensions表示卡片支持的外观规格,取值范围: - 1 * 2:表示1行2列的二宫格。 - 2 * 2:表示2行2列的四宫格。 - 2 * 4:表示2行4列的八宫格。 - 4 * 4:表示4行4列的十六宫格。字符串数组否defaultDimension表示卡片的默认外观规格,取值必须在该卡片supportDimensions设置的列表中。字符串否updateEnabled表示卡片是否支持周期性刷新,取值范围: - true:表示支持周期性刷新,可以在定时刷新(updateDuration)和定点刷新(scheduledUpdateTime)两种方式任选其一,优先选择定时刷新。 - false:表示不支持周期性刷新。布尔类型否scheduledUpdateTime表示卡片的定点刷新的时刻,采用24小时制,准确到分钟。 updateDuration参数优先级高于scheduledUpdateTime,两者同时设置时,以updateDuration设置的刷新时间为准。字符串可缺省,缺省值为“0:0”。updateDuration表示卡片定时刷新的更新周期,单元为30分钟,取值为天然数。 当取值为0时,表示该参数不生效。 当取值为正整数N时,表示刷新周期为30*N分钟。 updateDuration参数优先级高于scheduledUpdateTime,两者同时设置时,以updateDuration设置的刷新时间为准。数值可缺省,缺省值为“0”。formConfigAbility表示卡片的设置跳转链接,采用URI格式。字符串可缺省,缺省值为空。formVisibleNotify标识是否允许卡片使用卡片可见性通知。字符串可缺省,缺省值为空。metaData表示卡片的自定义信息,包罗customizeData数组标签。对象可缺省,缺省值为空。设置示例如下:
- {
- "forms": [
- {
- "name": "WidgetJS",
- "description": "$string:JSCardEntryAbility_desc",
- "src": "./js/WidgetJS/pages/index/index",
- "window": {
- "designWidth": 720,
- "autoDesignWidth": true
- },
- "colorMode": "auto",
- "isDefault": true,
- "updateEnabled": true,
- "scheduledUpdateTime": "10:30",
- "updateDuration": 1,
- "defaultDimension": "2*2",
- "supportDimensions": [
- "2*2"
- ]
- }
- ]
- }
- json
复制代码 卡片信息的持久化
因大部分卡片提供方都不是常驻服务,只有在需要使用时才会被拉起获取卡片信息,且卡片管理服务支持对卡片进行多实例管理,卡片ID对应实例ID,因此若卡片提供方支持对卡片数据进行设置,则需要对卡片的业务数据按照卡片ID进行持久化管理,以便在后续获取、更新以及拉起时能获取到正确的卡片业务数据。
- import { common, Want } from '@kit.AbilityKit';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { formBindingData, FormExtensionAbility } from '@kit.FormKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- import { preferences } from '@kit.ArkData';
- const TAG: string = 'JsCardFormAbility';
- const DATA_STORAGE_PATH: string = '/data/storage/el2/base/haps/form_store';
- const DOMAIN_NUMBER: number = 0xFF00;
- let storeFormInfo = async (formId: string, formName: string, tempFlag: boolean, context: common.FormExtensionContext): Promise<void> => {
- // 此处仅对卡片ID:formId,卡片名:formName和是否为临时卡片:tempFlag进行了持久化
- let formInfo: Record<string, string | boolean | number> = {
- 'formName': formName,
- 'tempFlag': tempFlag,
- 'updateCount': 0
- };
- try {
- const storage: preferences.Preferences = await preferences.getPreferences(context, DATA_STORAGE_PATH);
- // put form info
- await storage.put(formId, JSON.stringify(formInfo));
- hilog.info(DOMAIN_NUMBER, TAG, `[EntryFormAbility] storeFormInfo, put form info successfully, formId: ${formId}`);
- await storage.flush();
- } catch (err) {
- hilog.error(DOMAIN_NUMBER, TAG, `[EntryFormAbility] failed to storeFormInfo, err: ${JSON.stringify(err as BusinessError)}`);
- }
- }
- export default class JsCardFormAbility extends FormExtensionAbility {
- onAddForm(want: Want): formBindingData.FormBindingData {
- hilog.info(DOMAIN_NUMBER, TAG, '[JsCardFormAbility] onAddForm');
- if (want.parameters) {
- let formId = JSON.stringify(want.parameters['ohos.extra.param.key.form_identity']);
- let formName = JSON.stringify(want.parameters['ohos.extra.param.key.form_name']);
- let tempFlag = want.parameters['ohos.extra.param.key.form_temporary'] as boolean;
- // 将创建的卡片信息持久化,以便在下次获取/更新该卡片实例时进行使用
- // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
- storeFormInfo(formId, formName, tempFlag, this.context);
- }
- let obj: Record<string, string> = {
- 'title': 'titleOnCreate',
- 'detail': 'detailOnCreate'
- };
- let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
- return formData;
- }
- }
- ts
复制代码 且需要适配onRemoveForm卡片删除通知接口,在其中实现卡片实例数据的删除。
- import { common } from '@kit.AbilityKit';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { FormExtensionAbility } from '@kit.FormKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- import { preferences } from '@kit.ArkData';
- const TAG: string = 'JsCardFormAbility';
- const DATA_STORAGE_PATH: string = '/data/storage/el2/base/haps/form_store';
- const DOMAIN_NUMBER: number = 0xFF00;
- let deleteFormInfo = async (formId: string, context: common.FormExtensionContext): Promise<void> => {
- try {
- const storage: preferences.Preferences = await preferences.getPreferences(context, DATA_STORAGE_PATH);
- // del form info
- await storage.delete(formId);
- hilog.info(DOMAIN_NUMBER, TAG, `[EntryFormAbility] deleteFormInfo, del form info successfully, formId: ${formId}`);
- await storage.flush();
- } catch (err) {
- hilog.error(DOMAIN_NUMBER, TAG, `[EntryFormAbility] failed to deleteFormInfo, err: ${JSON.stringify(err as BusinessError)}`);
- };
- };
- export default class JsCardFormAbility extends FormExtensionAbility {
- onRemoveForm(formId: string): void {
- // 删除卡片实例数据
- hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onRemoveForm');
- // 删除之前持久化的卡片实例数据
- // 此接口请根据实际情况实现,具体请参考:FormExtAbility Stage模型卡片实例
- deleteFormInfo(formId, this.context);
- }
- }
- ts
复制代码 需要留意的是,卡片使用方在请求卡片时传递给提供方应用的Want数据中存在临时标记字段,表示此次请求的卡片是否为临时卡片:
- 常态卡片:卡片使用方会持久化的卡片。
- 临时卡片:卡片使用方不会持久化的卡片。
由于临时卡片的数据具有非持久化的特别性,某些场景例如卡片服务框架殒命重启,此时临时卡片数据在卡片管理服务中已经删除,且对应的卡片ID不会通知到提供方,以是卡片提供方需要本身负责清理长时间未删除的临时卡片数据。同时对应的卡片使用方可能会将之前请求的临时卡片转换为常态卡片。假如转换乐成,卡片提供方也需要对对应的临时卡片ID进行处理,把卡片提供方记录的临时卡片数据转换为常态卡片数据,防止提供方在清理长时间未删除的临时卡片时,把已经转换为常态卡片的临时卡片信息删除,导致卡片信息丢失。
卡片数据交互
当卡片应用需要更新数据时(如触发了定时更新或定点更新),卡片应用获取最新数据,并调用updateForm()接口主动触发卡片的更新。
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import { formBindingData, FormExtensionAbility, formProvider } from '@kit.FormKit';
- import { BusinessError } from '@kit.BasicServicesKit';
- const TAG: string = 'JsCardFormAbility';
- const DOMAIN_NUMBER: number = 0xFF00;
- export default class EntryFormAbility extends FormExtensionAbility {
- onUpdateForm(formId: string): void {
- // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
- hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onUpdateForm');
- let obj: Record<string, string> = {
- 'title': 'titleOnUpdate',
- 'detail': 'detailOnUpdate'
- };
- let formData: formBindingData.FormBindingData = formBindingData.createFormBindingData(obj);
- formProvider.updateForm(formId, formData).catch((error: BusinessError) => {
- hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] updateForm, error:' + JSON.stringify(error));
- });
- }
- }
- ts
复制代码 开发卡片页面
开发者可以使用类Web范式(HML+CSS+JSON)开发JS卡片页面。天生如下卡片页面,可以这样设置卡片页面文件:

- HML:使用类Web范式的组件形貌卡片的页面信息。
- <div class="container">
- <stack>
- <div class="container-img">
- <image src="/common/widget.png" class="bg-img"></image>
- </div>
- <div class="container-inner">
- <text class="title">{{title}}</text>
- <text class="detail_text" onclick="routerEvent">{{detail}}</text>
- </div>
- </stack>
- </div>
- html
复制代码 - CSS:HML中类Web范式组件的样式信息。
- .container {
- flex-direction: column;
- justify-content: center;
- align-items: center;
- }
- .bg-img {
- flex-shrink: 0;
- height: 100%;
- }
- .container-inner {
- flex-direction: column;
- justify-content: flex-end;
- align-items: flex-start;
- height: 100%;
- width: 100%;
- padding: 12px;
- }
- .title {
- font-size: 19px;
- font-weight: bold;
- color: white;
- text-overflow: ellipsis;
- max-lines: 1;
- }
- .detail_text {
- font-size: 16px;
- color: white;
- opacity: 0.66;
- text-overflow: ellipsis;
- max-lines: 1;
- margin-top: 6px;
- }
- css
复制代码 - JSON:卡片页面中的数据和事件交互。
- {
- "data": {
- "title": "TitleDefault",
- "detail": "TextDefault"
- },
- "actions": {
- "routerEvent": {
- "action": "router",
- "abilityName": "EntryAbility",
- "params": {
- "message": "add detail"
- }
- }
- }
- }
- json
复制代码 开发卡片事件
卡片支持为组件设置交互事件(action),包括router事件和message事件,其中router事件用于UIAbility跳转,message事件用于卡片开发职员自定义点击事件。
关键步骤说明如下:
- 在HML中为组件设置onclick属性,其值对应到JSON文件的actions字段中。
- 设置router事件:
- action属性值为"router"。
- abilityName为跳转目标的UIAbility名(支持跳转FA模子的PageAbility组件和Stage模子的UIAbility组件),如目前DevEco Studio创建的Stage模子的UIAbility默认名为EntryAbility。
- params为传递给跳转目标UIAbility的自定义参数,可以按需填写。其值可以在目标UIAbility启动时的want中的parameters里获取。如Stage模子MainAbility的onCreate生命周期里的入参want的parameters字段下获取到设置的参数。
- 设置message事件:
- action属性值为"message"。
- params为message事件的用户自定义参数,可以按需填写。其值可以在卡片生命周期函数onFormEvent()中的message里获取。
示例如下。
- HML文件
- <div class="container">
- <stack>
- <div class="container-img">
- <image src="/common/CardWebImg.png" class="bg-img"></image>
- <image src="/common/CardWebImgMatrix.png"
- class="bottom-img"/>
- </div>
- <div class="container-inner">
- <text class="title" onclick="routerEvent">{{ title }}</text>
- <text class="detail_text" onclick="messageEvent">{{ detail }}</text>
- </div>
- </stack>
- </div>
- html
复制代码 - CSS文件
- .container {
- flex-direction: column;
- justify-content: center;
- align-items: center;
- }
- .bg-img {
- flex-shrink: 0;
- height: 100%;
- z-index: 1;
- }
- .bottom-img {
- position: absolute;
- width: 150px;
- height: 56px;
- top: 63%;
- background-color: rgba(216, 216, 216, 0.15);
- filter: blur(20px);
- z-index: 2;
- }
- .container-inner {
- flex-direction: column;
- justify-content: flex-end;
- align-items: flex-start;
- height: 100%;
- width: 100%;
- padding: 12px;
- }
- .title {
- font-family: HarmonyHeiTi-Medium;
- font-size: 14px;
- color: rgba(255, 255, 255, 0.90);
- letter-spacing: 0.6px;
- font-weight: 500;
- width: 100%;
- text-overflow: ellipsis;
- max-lines: 1;
- }
- .detail_text {
- ffont-family: HarmonyHeiTi;
- font-size: 12px;
- color: rgba(255, 255, 255, 0.60);
- letter-spacing: 0.51px;
- font-weight: 400;
- text-overflow: ellipsis;
- max-lines: 1;
- margin-top: 6px;
- width: 100%;
- }
- css
复制代码 - JSON文件
- {
- "data": {
- "title": "TitleDefault",
- "detail": "TextDefault"
- },
- "actions": {
- "routerEvent": {
- "action": "router",
- "abilityName": "JSCardEntryAbility",
- "params": {
- "info": "router info",
- "message": "router message"
- }
- },
- "messageEvent": {
- "action": "message",
- "params": {
- "detail": "message detail"
- }
- }
- }
- }
- json
复制代码 说明:
"data"中JSON Value支持多级嵌套数据,在更新数据时,需要留意携带完备数据。
例如:当前卡片表现07.18日Mr.Zhang的课程信息,示例如下。
- "data": {
- "Day": "07.18",
- "teacher": {
- "name": "Mr.Zhang",
- "course": "Math"
- }
- }
- ts
复制代码 当卡片内容需要更新为07.18日Mr.Li的课程信息时,需要传递待更新的完备数据,不能只传递单个数据项,如只传name或只传course,示例如下。
- "teacher": {
- "name": "Mr.Li",
- "course": "English"
- }
- ts
复制代码 - 在UIAbility中接收router事件并获取参数
- import UIAbility from '@ohos.app.ability.UIAbility';
- import AbilityConstant from '@ohos.app.ability.AbilityConstant';
- import Want from '@ohos.app.ability.Want';
- import hilog from '@ohos.hilog';
- const TAG: string = 'EtsCardEntryAbility';
- const DOMAIN_NUMBER: number = 0xFF00;
- export default class EtsCardEntryAbility extends UIAbility {
- onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
- if (want.parameters) {
- let params: Record<string, Object> = JSON.parse(JSON.stringify(want.parameters.params));
- // 获取router事件中传递的info参数
- if (params.info === 'router info') {
- // 执行业务逻辑
- hilog.info(DOMAIN_NUMBER, TAG, `router info: ${params.info}`);
- }
- // 获取router事件中传递的message参数
- if (params.message === 'router message') {
- // 执行业务逻辑
- hilog.info(DOMAIN_NUMBER, TAG, `router message: ${params.message}`);
- }
- }
- }
- };
- ts
复制代码 - 在FormExtensionAbility中接收message事件并获取参数
- import FormExtension from '@ohos.app.form.FormExtensionAbility';
- import hilog from '@ohos.hilog';
- const TAG: string = 'FormAbility';
- const DOMAIN_NUMBER: number = 0xFF00;
- export default class FormAbility extends FormExtension {
- onFormEvent(formId: string, message: string): void {
- // 若卡片支持触发事件,则需要重写该方法并实现对事件的触发
- hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onFormEvent');
- // 获取message事件中传递的detail参数
- let msg: Record<string, string> = JSON.parse(message);
- if (msg.detail === 'message detail') {
- // 执行业务逻辑
- hilog.info(DOMAIN_NUMBER, TAG, 'message info:' + msg.detail);
- }
- }
- };
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |