HarmonyOS開發實例—蜜蜂AI助手(下篇)

CSDN 2023-12-15 11:00:44
5. 服務卡片

5.1 服務卡片

服務卡片(以下簡稱“卡片”)是一種界面展示形式,可以將應用的重要信息或操作前置到卡片,以達到服務直達、減少體驗層級的目的。卡片常用于嵌入到其他應用(當前卡片使用方只支持系統應用,如桌面)中作爲其界面顯示的一部分,並支持拉起頁面、發送消息等基礎的交互功能。

服務卡片架構

下圖爲服務卡片架構

另外了解卡片概念有助于我們更好的使用服務卡片。

卡片的基本概念:

卡片使用方:如上圖中的桌面,顯示卡片內容的宿主應用,控制卡片在宿主中展示的位置。

–   應用圖標:應用入口圖標,點擊後可拉起應用進程,圖標內容不支持交互。

–  卡片:具備不同規格大小的界面展示,卡片的內容可以進行交互,如實現按鈕進行界面的刷新、應用的跳轉等。

卡片提供方:包含卡片的應用,提供卡片的顯示內容、控件布局以及控件點擊處理邏輯。

–   FormExtensionAbility:卡片業務邏輯模塊,提供卡片創建、銷毀、刷新等生命周期回調。

–   卡片頁面:卡片 UI 模塊,包含頁面控件、布局、事件等顯示和交互信息。

動態卡片事件能力說明

針對動態卡片,ArkTS 卡片中提供了postCardAction()接口用于卡片內部和提供方應用間的交互,當前支持 router、message 和 call 三種類型的事件,僅在卡片中可以調用。後面我們也會用到這一塊的內容。

5.2 服務卡片創建方式

創建工程時,選擇Atomic Service,默認自帶卡片,也可以在創建工程後右鍵新建卡片。

另外就是我們可能不止一個卡片,所以,後續我們可以這樣創建服務卡片。

卡片相關的配置文件主要包含FormExtensionAbility 的配置和卡片的配置兩部分。

卡片需要在module.json5 配置文件中的extensionAbilities 標簽下,配置FormExtensionAbility 相關信息。FormExtensionAbility需要填寫 metadata元信息標簽,其中鍵名稱爲固定字符串“ohos.extension.form”,資源爲卡片的具體配置信息的索引。

{"module":{..."extensionAbilities":[{"name":"EntryFormAbility","srcEntry":"./ets/entryformability/EntryFormAbility.ets","label":"$string:EntryFormAbility_label","description":"$string:EntryFormAbility_desc","type":"form","metadata":[{"name":"ohos.extension.form","resource":"$profile:form_config"}]}]}}

卡片的具體配置信息。在上述FormExtensionAbility 的元信息(“metadata”配置項)中,可以指定卡片具體配置信息的資源索引。例如當 resource 指定爲$profile:form_config時,會使用開發視圖的resources/base/profile/目錄下的form_config.json 作爲卡片 profile 配置文件。內部字段結構說明如下表所示。

卡片form_config.json 配置文件

屬性名稱

含義

數據類型

是否可缺省

name

表示卡片的名稱,字符串最大長度爲 127 字節。

字符串

description

表示卡片的描述。取值可以是描述性內容,也可以是對描述性內容的資源索引,以支持多語言。字符串最大長度爲 255 字節。

字符串

可缺省,缺省爲空。

src

表示卡片對應的 UI 代碼的完整路徑。當爲 ArkTS 卡片時,完整路徑需要包含卡片文件的後綴,如:"./ets/widget/pages/WidgetCard.ets"。當爲 JS 卡片時,完整路徑無需包含卡片文件的後綴,如:"./js/widget/pages/WidgetCard"

字符串

uiSyntax

表示該卡片的類型,當前支持如下兩種類型:- arkts:當前卡片爲 ArkTS 卡片。- hml:當前卡片爲 JS 卡片。

字符串

可缺省,缺省值爲 hml

window

用于定義與顯示窗口相關的配置。

對象

可缺省,缺省值見表 2。

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 配置的刷新時間爲准。

字符串

可缺省,缺省時不進行定點刷新。

updateDuration

表示卡片定時刷新的更新周期,單位爲 30 分鍾,取值爲自然數。當取值爲 0 時,表示該參數不生效。當取值爲正整數 N 時,表示刷新周期爲 30*N 分鍾。> 說明:> updateDuration 參數優先級高于 scheduledUpdateTime,兩者同時配置時,以 updateDuration 配置的刷新時間爲准。

數值

可缺省,缺省值爲“0”。

formConfigAbility

表示卡片的配置跳轉鏈接,采用 URI 格式。

字符串

可缺省,缺省值爲空。

metadata

表示卡片的自定義信息,參考 Metadata 數組標簽。

對象

可缺省,缺省值爲空。

dataProxyEnabled

表示卡片是否支持卡片代理刷新,取值範圍:- true:表示支持代理刷新。- false:表示不支持代理刷新。設置爲 true 時,定時刷新和下次刷新不生效,但不影響定點刷新。

布爾類型

可缺省,缺省值爲 false。

isDynamic

表示此卡片是否爲動態卡片(僅針對 ArkTS 卡片生效)。- true:爲動態卡片 。- false:爲靜態卡片。

布爾類型

可缺省,缺省值爲 true。

transparencyEnabled

表示是否支持卡片使用方設置此卡片的背景透明度(僅對系統應用的 ArkTS 卡片生效。)。- true:支持設置背景透明度 。- false:不支持設置背景透明度。

布爾類型

可缺省,缺省值爲 false。

{"forms":[{"uiSyntax":"arkts","isDefault":true,"defaultDimension":"1*2","scheduledUpdateTime":"00:00","src":"./ets/jianguoaizhushoutuijian/jianguoaizhushoutuijian.ets","name":"jianguoaizhushoutuijian","description":"蜜蜂AI助手推薦","window":{"designWidth":720,"autoDesignWidth":true},"supportDimensions":["1*2"],"updateEnabled":true,"updateDuration":0},{"uiSyntax":"arkts","isDefault":false,"defaultDimension":"2*2","src":"./ets/jianguoaizhushou/jianguoaizhushou.ets","name":"jianguoaizhushou","description":"蜜蜂AI助手,幫你所幫","window":{"designWidth":720,"autoDesignWidth":true},"supportDimensions":["2*2"],"updateEnabled":false,"updateDuration":0},{"name":"poetry","description":"蜂蜜AI助手助你學妙語.","src":"./ets/poetry/pages/PoetryCard.ets","uiSyntax":"arkts","window":{"designWidth":720,"autoDesignWidth":true},"colorMode":"auto","isDefault":false,"updateEnabled":false,"scheduledUpdateTime":"10:30","updateDuration":1,"defaultDimension":"2*4","supportDimensions":["2*4"]},{"name":"history","description":"蜂蜜AI助手曆史記錄","src":"./ets/history/pages/HistoryCard.ets","uiSyntax":"arkts","window":{"designWidth":720,"autoDesignWidth":true},"colorMode":"auto","isDefault":false,"updateEnabled":false,"scheduledUpdateTime":"10:30","updateDuration":1,"defaultDimension":"4*4","supportDimensions":["4*4"]}]}

5.3 實現2*2/2*4/4*4 服務卡片

1-2 卡片

首先我們來看 1-2 卡片的實現。

@Entry@Componentstruct Jianguoaizhushoutuijian {private readonly PAGE_FULL: string = "100%";private readonly SIZE_4: number = 4;build() {Row({ space: this.SIZE_4 }) {Image('/common/imgs/ic_user.svg').width($r('app.float.size_32')).height($r('app.float.size_32'))Column() {Text('蜜蜂AI助手').fontSize($r('app.float.font_size_14')).fontColor($r('app.color.main_color')).fontWeight(FontWeight.Bolder)Text('知識百科/文本翻譯/...').fontSize($r('app.float.font_size_12')).fontColor($r('app.color.text_color_9'))}.height(this.PAGE_FULL).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Start)}.width(this.PAGE_FULL).height(this.PAGE_FULL).padding({left: $r('app.float.size_8'),right: $r('app.float.size_8')}).onClick(() => {postCardAction(this, {"action": "router","abilityName": "EntryAbility","params": {}});})}}

效果

實現效果如圖所示

原理

我可以用 router 來進行跳轉,默認不傳遞任何參數,就會跳轉到首頁。

.onClick(()=>{postCardAction(this,{"action":"router","abilityName":"EntryAbility","params":{}});})

2-4 的卡片

我們來看妙語集這一個2-4 卡片的實現。

完整代碼

conststorage=newLocalStorage();@Entry(storage)@Componentstruct PoetryCard {readonlyPAGE_FULL:string="100%";readonlyPRE_96:string="96%";readonlySIZE_40:number=40;readonlySIZE_30:number=30;readonlySIZE_20:number=20;readonlySIZE_16:number=16;readonlySIZE_8:number=8;readonlySIZE_4:number=4;@LocalStorageProp("poetry")poetry:any={content:"秀樾橫塘十裏香,水花晚色靜年芳。",author:"蔡松年",origin:"鹧鸪天·賞荷",category:"古詩文-四季-夏天"};build() {Column() {Row({space:this.SIZE_8}) {Image("/common/imgs/ic_ai_home.svg").width(this.SIZE_20).height(this.SIZE_20).fillColor($r('app.color.text_font_color'))Text('妙語集').fontSize($r('app.float.font_size_14')).fontColor($r('app.color.text_font_color'))}.width(this.PAGE_FULL).height(this.SIZE_40).linearGradient({angle:45,colors:[[$r('app.color.main_color'),0.1],[$r('app.color.auxiliary_color'),1.0]]}).padding({left:this.SIZE_16,right:this.SIZE_16})Column() {Stack({alignContent:Alignment.TopEnd}) {Column({space:this.SIZE_8}) {Text(this.poetry['origin']).fontSize($r('app.float.font_size_18')).fontWeight(FontWeight.Bolder).fontColor($r('app.color.text_color_title'))Text(this.poetry['author']).fontSize($r('app.float.font_size_14')).fontWeight(FontWeight.Medium).fontColor($r('app.color.text_color_9'))Text(this.poetry['content']).fontSize($r('app.float.font_size_16')).fontColor($r('app.color.text_color_title'))}.width(this.PRE_96).height(this.PRE_96).justifyContent(FlexAlign.Center)Button({type:ButtonType.Capsule}) {Image($r('app.media.ic_refreshing')).width(this.SIZE_20).height(this.SIZE_20).fillColor(Color.White)}.width(this.SIZE_30).height(this.SIZE_30).backgroundColor($r('app.color.tip_color')).onClick(()=>{postCardAction(this,{'action':'message','params':{'function':'refreshing'}})})}}.width(this.PAGE_FULL).flexShrink(1).padding({top:this.SIZE_4,bottom:this.SIZE_8})}.width(this.PAGE_FULL).height(this.PAGE_FULL)}}

效果

原理

我們是如何實現數據刷新的呢?

我們首先判斷返回的 functionName,如果是 refreshing,那麽我們就去請求網絡接口,並完成數據的顯示和刷新。具體的關鍵代碼如下所示。

if(functionName==="refreshing") {fetchGetPoetry().then((ret)=>{letformData={poetry:{}}LogUtil.info(`widget refreshing:${ret}`);constresult=JSON.parse(retasstring);if(result.code===200) {constpoetry:PoetryDto=result['data'];formData.poetry=poetry;}letformBD=formBindingData.createFormBindingData(formData);formProvider.updateForm(formId,formBD);})}

4-4 的卡片

完整代碼

@Entry@Componentstruct HistoryCard {readonlyPAGE_FULL:string="100%";readonlyPRE_96:string="96%";readonlySIZE_81:number=81;readonlySIZE_64:number=64;readonlySIZE_48:number=48;readonlySIZE_32:number=32;readonlySIZE_24:number=24;readonlySIZE_16:number=16;readonlySIZE_8:number=8;readonlySIZE_4:number=4;readonlyDEFAULT_AI_APP_LIST:Array<AiAppConfig>=[{appId:"6548c7fdeb28cf9c75531f66",chatId:"",name:"知識百科小助手",avatar:"/common/imgs/ic_wiki.svg",intro:"知識百科小助手。"},{appId:"65488134eb28cf9c75530e48",chatId:"",name:"節日小助手",avatar:"/common/imgs/ic_festival.svg",intro:"節日小助手。"},{appId:"65487d64eb28cf9c75530cd2",chatId:"",name:"文本翻譯助手",avatar:"/common/imgs/ic_document.svg",intro:"文本翻譯助手。"},{appId:"654ed429ab7249585cd2cab7",chatId:"",name:"産品名稱助手",avatar:"/common/imgs/ic_product.svg",intro:"産品名稱助手。"},{appId:"654ed4c3ab7249585cd2caf4",chatId:"",name:"道歉信助手",avatar:"/common/imgs/ic_sorry.svg",intro:"道歉信助手。"}];build() {Column({space:this.SIZE_8}) {Row({space:this.SIZE_4}) {Image($r('app.media.ic_history')).width(this.SIZE_24).height(this.SIZE_24).fillColor($r('app.color.main_color'))Text('查看曆史數據').fontSize($r('app.float.font_size_16')).fontColor($r('app.color.main_color')).fontWeight(FontWeight.Bolder)}.width(this.PAGE_FULL).height(this.SIZE_48).padding({left:this.SIZE_16})Column() {GridRow({columns:3,gutter:{x:this.SIZE_4,y:this.SIZE_4}}) {ForEach(this.DEFAULT_AI_APP_LIST,(item:AiAppConfig)=>{GridCol() {Column({space:this.SIZE_8}) {Image(item.avatar).width(this.SIZE_32).height(this.SIZE_32).fillColor($r('app.color.main_color'))Text(item.name).fontSize($r('app.float.font_size_12')).fontColor($r('app.color.auxiliary_color')).fontWeight(FontWeight.Bold)}.width(this.PAGE_FULL).height(this.SIZE_81).justifyContent(FlexAlign.Center).onClick(()=>{postCardAction(this,{'action':'router','abilityName':'HistoryAbility','params':{'targetPage':'history','aiApp':item}})})}.borderRadius(this.SIZE_8).padding({left:this.SIZE_4,right:this.SIZE_4,top:this.SIZE_8,bottom:this.SIZE_4}).shadow({radius:this.SIZE_8,color:$r('app.color.tab_default_color')})})}}.width(this.PRE_96).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).flexShrink(1)}.width(this.PAGE_FULL).height(this.PAGE_FULL)}}/*** AI應用配置*/interfaceAiAppConfig {appId:string;// AI應用AppIdchatId:string;//會話窗口IDname:string;// AI應用名稱avatar:string;// AI應用LOGOintro?:string;// AI應用介紹}interfaceChatHistory {chat:AiAppConfig;total:number;}

效果

原理

在卡片中使用postCardAction接口的 router 能力,能夠快速拉起卡片提供方應用的指定 UIAbility,因此 UIAbility 較多的應用往往會通過卡片提供不同的跳轉按鈕,實現一鍵直達的效果。

通常使用按鈕控件來實現頁面拉起,

@Entry@Componentstruct WidgetCard {build() {Column() {Button('跳轉').onClick(()=>{console.info('Jump to EntryAbility funA');postCardAction(this,{action:'router',abilityName:'EntryAbility',//只能跳轉到當前應用下的UIAbilityparams:{targetPage:'funA'//在EntryAbility中處理這個信息}});})}.width('100%').height('100%').justifyContent(FlexAlign.SpaceAround)}}

在 UIAbility 中接收 router 事件並獲取參數,根據傳遞的 params 不同,選擇拉起不同的頁面。

importUIAbilityfrom'@ohos.app.ability.UIAbility';importwindowfrom'@ohos.window';importWantfrom'@ohos.app.ability.Want';importBasefrom'@ohos.base';importAbilityConstantfrom'@ohos.app.ability.AbilityConstant';letselectPage:string='';letcurrentWindowStage:window.WindowStage|null=null;exportdefaultclassEntryAbilityextendsUIAbility {//如果UIAbility第一次啓動,在收到Router事件後會觸發onCreate生命周期回調onCreate(want:Want,launchParam:AbilityConstant.LaunchParam) {//獲取router事件中傳遞的targetPage參數console.info('onCreate want:'+JSON.stringify(want));if(want.parameters?.params!==undefined) {letparams:Record<string,string>=JSON.parse(want.parameters?.params.toString());console.info('onCreate router targetPage:'+params.targetPage);selectPage=params.targetPage;}}//如果UIAbility已在後台運行,在收到Router事件後會觸發onNewWant生命周期回調onNewWant(want:Want,launchParam:AbilityConstant.LaunchParam) {console.info('onNewWant want:'+JSON.stringify(want));if(want.parameters?.params!==undefined) {letparams:Record<string,string>=JSON.parse(want.parameters?.params.toString());console.info('onNewWant router targetPage:'+params.targetPage);selectPage=params.targetPage;}if(currentWindowStage!=null) {this.onWindowStageCreate(currentWindowStage);}}onWindowStageCreate(windowStage:window.WindowStage) {lettargetPage:string;//根據傳遞的targetPage不同,選擇拉起不同的頁面switch(selectPage) {case'funA':targetPage='pages/FunA';break;case'funB':targetPage='pages/FunB';break;default:targetPage='pages/Index';}if(currentWindowStage===null) {currentWindowStage=windowStage;}windowStage.loadContent(targetPage,(err:Base.BusinessError)=>{if(err&&err.code) {console.info('Failed to load the content. Cause: %{public}s',JSON.stringify(err));return;}});}}

6 總結

通過蜜蜂 AI 助手元服務的開發,我們體驗到了端雲一體化帶來的便捷,尤其注冊登陸這一塊,有了雲端的接入,我們可以很快的加入。另外在項目裏我們還用到了低碼能力,不用一行代碼,就完成了手機號登陸的功能。

本次鴻蒙和 AI 的結合,給了我新的體驗。大家也可以自行嘗試下 HarmonyOS 的開發,會給你帶來不一樣的體驗。

0 阅读:0

CSDN

簡介:成就一億技術人,成爲技術人交流與成長的家園