自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

HarmonyOS開發(fā)實(shí)例—蜜蜂AI助手

系統(tǒng) OpenHarmony
HarmonyOS是華為公司開發(fā)的操作系統(tǒng),它的設(shè)計(jì)理念是面向未來的全場景智慧體驗(yàn),可在各種設(shè)備上運(yùn)行,包括手機(jī)、平板電腦、智能手表、智能音箱等。

想了解更多關(guān)于開源的內(nèi)容,請?jiān)L問:

51CTO 鴻蒙開發(fā)者社區(qū)

https://ost.51cto.com

一、前言

自華為宣布HarmonyOS NEXT全面啟動(dòng),近期新浪、B站、小紅書、支付寶等各領(lǐng)域頭部企業(yè)紛紛啟動(dòng)鴻蒙原生應(yīng)用開發(fā)。據(jù)媒體統(tǒng)計(jì),如今Top20的應(yīng)用里,已經(jīng)有近一半開始了鴻蒙原生應(yīng)用開發(fā)。雖然目前HarmonyOS NEXT還未面向個(gè)人開發(fā)者開放,但我們可以體驗(yàn)并使用最新的API9和開發(fā)工具,嘗試開發(fā)元服務(wù),這個(gè)鴻蒙新的應(yīng)用形態(tài)。體驗(yàn)未來在HarmonyOS NEXT上實(shí)現(xiàn)的應(yīng)用開發(fā)。但需要注意的是, 基于API9開發(fā)的應(yīng)用或元服務(wù)是不可以適配HarmonyOS NEXT版本的,大家也可以期待一下明年推出的適配HarmonyOS NEXT新版本。

本文主要是基于蜜蜂AI元服務(wù)的開發(fā)案例:主要的功能有

元服務(wù)內(nèi)部功能:

  • 提供兩個(gè)Tabs,首頁和我的。
  • 用戶只有登錄之后才可以去使用蜜蜂AI的功能。
  • 目前現(xiàn)有的知識(shí)庫包括知識(shí)百科小助手,節(jié)日小助手,文本翻譯小助手,產(chǎn)品名稱小助手,以及道歉信小助手等。
  • 用戶使用小助手之后,我們可以保存對(duì)話到列表內(nèi),下次快速的進(jìn)行訪問。

元服務(wù)卡片:

  • 提供2-4的卡片,卡片界面展示每日妙語,點(diǎn)擊即可刷新。
  • 提供1-2的卡片,實(shí)現(xiàn)快速訪問首頁。
  • 提供2-2卡片,可以快速使用包括知識(shí)百科小助手,節(jié)日小助手,文本翻譯小助手,產(chǎn)品名稱小助手。
  • 提供4-4卡片,可以快速到達(dá)登陸頁面,訪問小助手等。

1、HarmonyOS

HarmonyOS是華為公司開發(fā)的操作系統(tǒng),它的設(shè)計(jì)理念是面向未來的全場景智慧體驗(yàn),可在各種設(shè)備上運(yùn)行,包括手機(jī)、平板電腦、智能手表、智能音箱等。HarmonyOS采用分布式技術(shù),可以將不同設(shè)備之間的計(jì)算資源連接起來,實(shí)現(xiàn)設(shè)備間的協(xié)同工作,提高系統(tǒng)的性能和穩(wěn)定性。此外,HarmonyOS還擁有高度自適應(yīng)的界面、多屏協(xié)同等特性,使用戶能夠在不同設(shè)備上實(shí)現(xiàn)無縫的體驗(yàn)。

2、元服務(wù)

在萬物互聯(lián)時(shí)代,人均持有設(shè)備量不斷攀升,設(shè)備和場景的多樣性,使應(yīng)用開發(fā)變得更加復(fù)雜、應(yīng)用入口更加多樣。在此背景下,應(yīng)用提供方和用戶迫切需要一種新的服務(wù)提供方式,使應(yīng)用開發(fā)更簡單、服務(wù)(如聽音樂、打車等)的獲取和使用更便捷。為此,HarmonyOS除支持傳統(tǒng)方式的需要安裝的應(yīng)用(以下簡稱傳統(tǒng)應(yīng)用)外,還支持更加方便快捷的免安裝的應(yīng)用(即元服務(wù))。

3、介紹AppGallery Connect(AGC)

AppGallery Connect(簡稱AGC)致力于為應(yīng)用的創(chuàng)意、開發(fā)、分發(fā)、運(yùn)營、經(jīng)營各環(huán)節(jié)提供一站式服務(wù),構(gòu)建全場景智慧化的應(yīng)用生態(tài)體驗(yàn)。

4、蜜蜂AI元服務(wù)助手背景

目前AI正火,而我自己也想將鴻蒙和AI做一結(jié)合,于是有了蜜蜂這個(gè)作品。

元服務(wù)與傳統(tǒng)應(yīng)用對(duì)比

項(xiàng)目

元服務(wù)

傳統(tǒng)應(yīng)用

軟件包形態(tài)

App Pack(.app)

App Pack(.app)

分發(fā)平臺(tái)

由應(yīng)用市場(AppGallery)管理和分發(fā)

由應(yīng)用市場(AppGallery)管理和分發(fā)

安裝后有無桌面icon

無桌面icon,但可手動(dòng)添加到桌面,顯示形式為服務(wù)卡片

有桌面icon

HAP免安裝要求

所有HAP(包括Entry HAP和Feature HAP)均需滿足免安裝要求

所有HAP(包括Entry HAP和Feature HAP)均為非免安裝的

新建元服務(wù)應(yīng)用。

開通:

AI平臺(tái)https://fulitimes.com/登陸賬號(hào)17752170152

https://ai.fulitimes.com/model?modelId=

如何運(yùn)行

二、準(zhǔn)備工作

1、HarmonyOS應(yīng)用開發(fā)環(huán)境

工欲善其事,必先利其器,我們首先要做的就是搭建開發(fā)環(huán)境。

這里面我們分為三步走。

(1)環(huán)境安裝

首先在這邊安裝最新的IDE:

下載鏈接:https://developer.harmonyos.com/cn/develop/deveco-studio/#download。

我的是M1,所以我們下載這一個(gè)就可以。

(2)環(huán)境配置

下載完成之后,我們就開始配置開發(fā)環(huán)境。下載SDK及工具鏈,首次使用DevEco Studio,工具的配置向?qū)?huì)引導(dǎo)您下載SDK及工具鏈。配置向?qū)J(rèn)下載 API Version 9的SDK及工具鏈,我們選擇默認(rèn)就好。

下載nodejs和ohpm,記得最好HarmonyOS SDK路徑中不能包含中文字符。

下載完成之后,我們下載HarmonyOS SDK。

在彈出的SDK下載信息頁面,單擊Next,并在彈出的License Agreement窗口,閱讀License協(xié)議,需同意License協(xié)議后,單擊Next。

目前最新的應(yīng)該是3.2.13.5。

確認(rèn)設(shè)置項(xiàng)的信息,點(diǎn)擊Next開始安裝。

等待Node.js、ohpm和SDK下載完成后,單擊Finish,界面會(huì)進(jìn)入到DevEco Studio歡迎頁。

(3)創(chuàng)建HelloWord

在DevEco Studio的歡迎頁,選擇Create Project開始創(chuàng)建一個(gè)新工程。

根據(jù)工程創(chuàng)建向?qū)?,在HarmonyOS頁簽,選擇“Empty Ability”模板,單擊Next。

單擊Next,各個(gè)參數(shù)保持默認(rèn)值即可,單擊Finish。

(4)運(yùn)行Helloword

將搭載HarmonyOS手機(jī)與電腦連接。

單擊File>Project Structure >Project > SigningConfigs界面勾選“支持HarmonyOS,以及Automatically generate signature”,等待自動(dòng)簽名完成即可,單擊OK。如右所示:。

在編輯窗口右上角的工具欄,單擊運(yùn)行,等待編譯完成即可便運(yùn)行在設(shè)備上。

這個(gè)時(shí)候真機(jī)就可以看到HelloWord。接下來我們就創(chuàng)建蜜蜂AI元服務(wù)。

2、創(chuàng)建蜜蜂AI元服務(wù)

這里我們的模版就不再選空模板了,而是直接選擇最后一個(gè)端云一體化模版

然后其他的就按照上面的配置就可以。完成項(xiàng)目的配置。

這里有個(gè)區(qū)別就是我們需要關(guān)聯(lián)云資源。所以我們創(chuàng)建的應(yīng)用包名要牢記,這個(gè)要在后面我們云端配置的時(shí)候使用。

為工程關(guān)聯(lián)云開發(fā)所需的資源,即在DevEco Studio中選擇您的華為開發(fā)者賬號(hào)加入的開發(fā)者團(tuán)隊(duì),將該團(tuán)隊(duì)在AGC的同包名應(yīng)用關(guān)聯(lián)到當(dāng)前工程,具體操作如下:

  • 若尚未登錄DevEco Studio,單擊“Sign in”,拉起瀏覽器在彈出的賬號(hào)登錄頁面,使用已實(shí)名認(rèn)證的華為開發(fā)者賬號(hào)完成登錄。

單擊“Team”下拉框,選擇開發(fā)團(tuán)隊(duì)。選中團(tuán)隊(duì)后,系統(tǒng)根據(jù)工程包名自動(dòng)查詢團(tuán)隊(duì)下的同包名應(yīng)用。若為首次創(chuàng)建且團(tuán)隊(duì)下未創(chuàng)建同包名的應(yīng)用,則提示需要在AGC平臺(tái)創(chuàng)建應(yīng)用。

單擊“AppGallery Connect”打開AGC應(yīng)用創(chuàng)建向?qū)?,填寫?yīng)用信息,單擊“確認(rèn)”按鈕創(chuàng)建應(yīng)用。

完成以上操作后,DevEco Studio即可獲取到同包名應(yīng)用對(duì)應(yīng)的項(xiàng)目信息。

3、AGC配置

我們登陸云側(cè),創(chuàng)建元服務(wù)。

然后我們開通手機(jī)登陸和郵箱登錄服務(wù)。

三、實(shí)現(xiàn)登錄

當(dāng)前AGC認(rèn)證服務(wù)為HarmonyOS應(yīng)用/服務(wù)提供的登錄認(rèn)證方式有手機(jī)、郵箱兩種方式。本工程使用“手機(jī)號(hào)碼+驗(yàn)證碼”的方式作為應(yīng)用的登錄入口。而且我們在前面已經(jīng)開通。

在登陸這一塊,用戶首次登陸的時(shí)候,我們會(huì)首先利用首選項(xiàng)檢查他的登陸狀態(tài)。

首選項(xiàng)工具類

/**
 * 首選項(xiàng)操作類
 */
import { PreferenceDBUtil } from '../utils/PreferencesDBUtil';


const preDbService = new PreferenceDBUtil();
preDbService.getPreStorage();

export const getDBPre = async (key: string) => {
  const value = await preDbService.getPreVal(key);
  return value;
}

export const putDBPre = async (key: string, value: string) => {
  await preDbService.putPreData(key, value);
}

然后跳用調(diào)用AGConnectAuth.requestEmailVerifyCode申請驗(yàn)證碼,在entry/src/main/ets/services/Auth.ts認(rèn)證工具類中添加郵箱驗(yàn)證碼獲取方法。

import { MainPage } from "@hw-agconnect/auth-component-ohos"
import router from '@ohos.router'
import { LogUtil } from '../common/utils/LogUtil';
import { Constants } from '../common/Constants';
import { putPre } from '../common/service/PreService';
import { UserInfo } from '../common/UserInfo';

@Entry
@Component
struct Index {
  @State icon: Resource = router.getParams()['icon'];
  @State isAgreement:boolean = router.getParams()['isAgreement'];
  @State agreementContent:string = router.getParams()['agreementContent'];
  @State onSuccess: Function = router.getParams()['onSuccess'];
  @State onError: Function = router.getParams()['onError'];

  build() {
    Column() {
      MainPage({
        icon: this.icon,
        agreement: {
          isAgreement: this.isAgreement,
          agreementContent: this.agreementContent,
        },
        onSuccess: async (user) => {
          LogUtil.info(`登錄用戶信息:${JSON.stringify(user)}`);
          const loginUser = user['user'];
          const userInfo: UserInfo = {
            uid: loginUser['uid'],
            email: loginUser['email'],
            phone: loginUser['phone'] === undefined ? "" : loginUser['phone'].split('-')[1],
            displayName: loginUser['displayName'] === undefined ? "" : loginUser['displayName'],
            photoUrl: loginUser['photoUrl'] === undefined ? "/common/imgs/ic_user.svg" : loginUser['photoUrl']
          }
          await putPre(Constants.LOGIN_USER_KEY, JSON.stringify(userInfo));
          router.back();
        },
        onError: (err) => {
          LogUtil.error(`登錄用戶信息:${JSON.stringify(err)}`);
        }
      })
    }
  }

  aboutToAppear() {
  }
}

未登錄彈窗

/**
 * 未登錄彈窗
 */
import common from '@ohos.app.ability.common';
import router from '@ohos.router';
import { GlobalConstant } from '../common/constants/GlobalConstant';
@CustomDialog
export struct LoginTipDialogView {
  loginTipCtrl: CustomDialogController;
  build() {
    Column({ space: GlobalConstant.SIZE_8 }) {
      Row({ space: GlobalConstant.SIZE_4 }) {
        Image($r('app.media.ic_tip'))
          .width(GlobalConstant.SIZE_32)
          .height(GlobalConstant.SIZE_32)
        Text('溫馨提示')
          .fontSize($r('app.float.font_size_24'))
          .fontColor($r('app.color.tip_color'))
          .fontWeight(FontWeight.Bolder)
      }
      .width(GlobalConstant.PAGE_FULL)
      .height(GlobalConstant.SIZE_64)
      .padding({ left: GlobalConstant.SIZE_16 })
      Text('您還未登錄,請登錄后體驗(yàn)功能!')
        .height(GlobalConstant.SIZE_48)
        .fontSize(Color.Black)
        .fontSize($r('app.float.font_size_18'))
        .fontWeight(FontWeight.Normal)
      Row({ space: GlobalConstant.SIZE_8 }) {
        Button('退出', { type: ButtonType.Normal })
          .borderRadius(GlobalConstant.SIZE_4)
          .backgroundColor($r('app.color.embellishment_color'))
          .fontColor($r('app.color.text_color_9'))
          .onClick(() => {
            const ctx = getContext(this) as common.UIAbilityContext;
            ctx.terminateSelf();
          })
        Button('去登錄', { type: ButtonType.Normal })
          .borderRadius(GlobalConstant.SIZE_4)
          .backgroundColor($r('app.color.embellishment_color'))
          .fontColor($r('app.color.auxiliary_color'))
          .onClick(() => {
            this.loginTipCtrl.close();
            router.pushUrl({
              params:{
                isAgreement: true,
                agreementContent: "",
                icon: "",
                type: ["HWID_VERIFY_CODE","PHONE"]
              },
              url: '@bundle:com.jianguo.ai/common/ets/LoginComponent/LoginPage',
            })
          })
      }
      .width(GlobalConstant.PAGE_FULL)
      .justifyContent(FlexAlign.Center)
    }
    .width(GlobalConstant.PAGE_96)
    .padding({ bottom: GlobalConstant.SIZE_20 })
    .borderRadius(GlobalConstant.SIZE_16)
    .backgroundColor(Color.White)
  }
}

四、實(shí)現(xiàn)蜜蜂AI助手頁面

我們這個(gè)應(yīng)用主要的一個(gè)功能就是AI助手,所以這一塊我們分為三塊。

1、蜜蜂AI列表頁

關(guān)于列表頁,我們使用一個(gè)列表就可以。

/**
 * 首頁
 */
import { ConfigConstant } from '../common/constants/ConfigConstant'
import { GlobalConstant } from '../common/constants/GlobalConstant'
import { AiAppConfig } from '../common/dto/AiAppConfig';
import router from '@ohos.router'
import { getDBPre } from '../common/api/PreDbService';
@Component
export struct HomeView {

  @State aiAppList: Array<AiAppConfig> = ConfigConstant.DEFAULT_AI_APP_LIST;

  }

  build() {
    Column() {
      List() {
        ForEach(this.aiAppList, (item: AiAppConfig) => {
          ListItem() {
            Row({ space: GlobalConstant.SIZE_8 }) {
              Row() {
                Image(item.avatar)
                  .width(GlobalConstant.SIZE_64)
                  .height(GlobalConstant.SIZE_64)
                  .borderRadius(GlobalConstant.SIZE_32)
              }
              .height(GlobalConstant.PAGE_FULL)
              .layoutWeight(1)
              Column({ space: GlobalConstant.SIZE_16 }) {
                Text(item.name)
                  .fontSize($r('app.float.font_size_18'))
                Text(item.intro)
                  .fontSize($r('app.float.font_size_14'))
                  .fontColor($r('app.color.text_color_9'))
              }
              .height(GlobalConstant.PAGE_FULL)
              .layoutWeight(3)
              .justifyContent(FlexAlign.Center)
              .alignItems(HorizontalAlign.Start)
            }
            .width(GlobalConstant.PAGE_96)
            .height(GlobalConstant.SIZE_100)
            .paddingStyle()
            .borderRadius(GlobalConstant.SIZE_16)
            .shadow({
              radius: GlobalConstant.SIZE_16,
              color: $r('app.color.main_color')
            })
            .onClick(() => {
              router.pushUrl({
                url: "pages/detail/index",
                params: {
                  "AiAppConfig": item
                }
              })
            })
          }
          .width(GlobalConstant.PAGE_FULL)
          .paddingStyle()
          .borderRadius(GlobalConstant.SIZE_16)
        })
      }
      .listDirection(Axis.Vertical)
      
    }
    .width(GlobalConstant.PAGE_FULL)
    .height(GlobalConstant.PAGE_FULL)
    .padding(GlobalConstant.SIZE_8)
  }


}

效果圖

2、對(duì)話頁

關(guān)鍵代碼

build() {
    Column({ space: GlobalConstant.SIZE_8 }) {
      Stack({ alignContent: Alignment.Bottom }) {
        Column() {
          Column({ space: GlobalConstant.SIZE_4 }) {
            Text("蜜蜂AI助手")
              .fontSize($r('app.float.font_size_16'))
              .fontColor(Color.Black)
              .fontWeight(FontWeight.Bolder)
            Text("介紹")
              .fontSize($r('app.float.font_size_12'))
              .fontColor($r('app.color.text_color_9'))
              .fontWeight(FontWeight.Lighter)
          }
          .width(GlobalConstant.PAGE_FULL)
          .justifyContent(FlexAlign.Center)
          .padding({
            top: GlobalConstant.SIZE_4,
            bottom: GlobalConstant.SIZE_8
          })

          Scroll() {
            Column({ space: GlobalConstant.SIZE_8 }) {
              ForEach(this.chatContentArr, (chat: ChatInfo) => {
                if (chat.role === "assistant") {
                  Row() {
                    Row({ space: GlobalConstant.SIZE_8 }) {
                      Image(chat.avatar)
                        .width(GlobalConstant.SIZE_24)
                        .height(GlobalConstant.SIZE_24)
                      Row() {
                        Text(chat.content)
                          .fontSize($r('app.float.font_size_14'))
                          .fontColor(Color.Black)
                      }
                      .width(chat.content.length > 15 ? GlobalConstant.PAGE_76 : 'auto')
                      .backgroundColor($r('app.color.embellishment_color'))
                      .padding({
                        left: GlobalConstant.SIZE_16,
                        right: GlobalConstant.SIZE_16,
                        top: GlobalConstant.SIZE_8,
                        bottom: GlobalConstant.SIZE_8
                      })
                      .borderRadius({
                        topRight: GlobalConstant.SIZE_4,
                        bottomLeft: GlobalConstant.SIZE_8,
                        bottomRight: GlobalConstant.SIZE_4
                      })
                    }
                    .justifyContent(FlexAlign.Start)
                    .alignItems(VerticalAlign.Top)
                  }
                  .width(GlobalConstant.PAGE_FULL)
                  .justifyContent(FlexAlign.Start)
                }
                if (chat.role === "user") {
                  Row() {
                    Row({ space: GlobalConstant.SIZE_8 }) {
                      Row() {
                        Text(chat.content)
                          .fontSize($r('app.float.font_size_14'))
                          .fontColor(Color.Black)
                      }
                      .width(chat.content.length > 15 ? GlobalConstant.PAGE_76 : 'auto')
                      .backgroundColor($r('app.color.tab_default_color'))
                      .padding({
                        left: GlobalConstant.SIZE_16,
                        right: GlobalConstant.SIZE_16,
                        top: GlobalConstant.SIZE_8,
                        bottom: GlobalConstant.SIZE_8
                      })
                      .borderRadius({
                        topLeft: GlobalConstant.SIZE_4,
                        bottomLeft: GlobalConstant.SIZE_4,
                        bottomRight: GlobalConstant.SIZE_8
                      })
                      Image(chat.avatar)
                        .width(GlobalConstant.SIZE_24)
                        .height(GlobalConstant.SIZE_24)
                    }
                    .justifyContent(FlexAlign.End)
                    .alignItems(VerticalAlign.Top)
                  }
                  .width(GlobalConstant.PAGE_FULL)
                  .justifyContent(FlexAlign.End)
                }
              })
            }.width(GlobalConstant.PAGE_FULL)
          }
          .width(GlobalConstant.PAGE_96)
          .scrollable(ScrollDirection.Vertical)
          .flexShrink(1)
        }
        .width(GlobalConstant.PAGE_FULL)
        .height(GlobalConstant.PAGE_FULL)
        .padding({ bottom: GlobalConstant.SIZE_50 })

        Row({ space: GlobalConstant.SIZE_8 }) {
          TextInput({ placeholder: "請輸入提示詞...", text: this.inputValue })
            .height(GlobalConstant.SIZE_48)
            .fontSize($r('app.float.font_size_16'))
            .placeholderFont({ size: $r('app.float.font_size_16') })
            .placeholderColor($r('app.color.text_color_9'))
            .borderRadius($r('app.float.size_8'))
            .backgroundColor($r('app.color.card_bg_color'))
            .flexShrink(1)
            .onChange((value: string) => {
              this.inputValue = value;
            })
          Image($r('app.media.ic_send'))
            .width(GlobalConstant.SIZE_32)
            .height(GlobalConstant.SIZE_32)
            .onClick(async () => {
              this.loadingCtrl.open();
              if (this.inputValue === "") {
                promptAction.showToast({
                  message: "發(fā)送內(nèi)容不能為空!"
                })
                return;
              }
              await this.getAiResult();
            })
        }
        .width(GlobalConstant.PAGE_FULL)
        .padding({
          left: GlobalConstant.SIZE_8,
          right: GlobalConstant.SIZE_8
        })
        .backgroundColor($r('app.color.card_bg_color'))
      }
      .width(GlobalConstant.PAGE_FULL)
      .height(GlobalConstant.PAGE_FULL)
    }
    .width(GlobalConstant.PAGE_FULL)
    .height(GlobalConstant.PAGE_FULL)
  }

效果圖

加載中:

問答后:

五、服務(wù)卡片

1、服務(wù)卡片

服務(wù)卡片(以下簡稱“卡片”)是一種界面展示形式,可以將應(yīng)用的重要信息或操作前置到卡片,以達(dá)到服務(wù)直達(dá)、減少體驗(yàn)層級(jí)的目的??ㄆS糜谇度氲狡渌麘?yīng)用(當(dāng)前卡片使用方只支持系統(tǒng)應(yīng)用,如桌面)中作為其界面顯示的一部分,并支持拉起頁面、發(fā)送消息等基礎(chǔ)的交互功能。

服務(wù)卡片架構(gòu)

下圖為服務(wù)卡片架構(gòu)。

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

卡片的基本概念:

  • 卡片使用方:如上圖中的桌面,顯示卡片內(nèi)容的宿主應(yīng)用,控制卡片在宿主中展示的位置。
  • 應(yīng)用圖標(biāo):應(yīng)用入口圖標(biāo),點(diǎn)擊后可拉起應(yīng)用進(jìn)程,圖標(biāo)內(nèi)容不支持交互。
  • 卡片:具備不同規(guī)格大小的界面展示,卡片的內(nèi)容可以進(jìn)行交互,如實(shí)現(xiàn)按鈕進(jìn)行界面的刷新、應(yīng)用的跳轉(zhuǎn)等。
  • 卡片提供方:包含卡片的應(yīng)用,提供卡片的顯示內(nèi)容、控件布局以及控件點(diǎn)擊處理邏輯。
  • FormExtensionAbility:卡片業(yè)務(wù)邏輯模塊,提供卡片創(chuàng)建、銷毀、刷新等生命周期回調(diào)。
  • 卡片頁面:卡片UI模塊,包含頁面控件、布局、事件等顯示和交互信息。

動(dòng)態(tài)卡片事件能力說明

針對(duì)動(dòng)態(tài)卡片,ArkTS卡片中提供了postCardAction()接口用于卡片內(nèi)部和提供方應(yīng)用間的交互,當(dāng)前支持router、message和call三種類型的事件,僅在卡片中可以調(diào)用。后面我們也會(huì)用到這一塊的內(nèi)容。

2、服務(wù)卡片創(chuàng)建方式

創(chuàng)建工程時(shí),選擇Atomic Service,默認(rèn)自帶卡片,也可以在創(chuàng)建工程后右鍵新建卡片。

另外就是我們可能不止一個(gè)卡片,所以,后續(xù)我們可以這樣創(chuàng)建服務(wù)卡片。

卡片相關(guān)的配置文件主要包含F(xiàn)ormExtensionAbility的配置和卡片的配置兩部分。

卡片需要在module.json5配置文件中的extensionAbilities標(biāo)簽下,配置FormExtensionAbility相關(guān)信息。FormExtensionAbility需要填寫metadata元信息標(biāo)簽,其中鍵名稱為固定字符串“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”配置項(xiàng))中,可以指定卡片具體配置信息的資源索引。例如當(dāng)resource指定為$profile:form_config時(shí),會(huì)使用開發(fā)視圖的resources/base/profile/目錄下的form_config.json作為卡片profile配置文件。內(nèi)部字段結(jié)構(gòu)說明如下表所示。

卡片form_config.json配置文件

屬性名稱

含義

數(shù)據(jù)類型

是否可缺省

name

表示卡片的名稱,字符串最大長度為127字節(jié)。

字符串


description

表示卡片的描述。取值可以是描述性內(nèi)容,也可以是對(duì)描述性內(nèi)容的資源索引,以支持多語言。字符串最大長度為255字節(jié)。

字符串

可缺省,缺省為空。

src

表示卡片對(duì)應(yīng)的UI代碼的完整路徑。當(dāng)為ArkTS卡片時(shí),完整路徑需要包含卡片文件的后綴,如:“./ets/widget/pages/WidgetCard.ets”。當(dāng)為JS卡片時(shí),完整路徑無需包含卡片文件的后綴,如:“./js/widget/pages/WidgetCard”

字符串


uiSyntax

表示該卡片的類型,當(dāng)前支持如下兩種類型:- arkts:當(dāng)前卡片為ArkTS卡片。- hml:當(dāng)前卡片為JS卡片。

字符串

可缺省,缺省值為hml

window

用于定義與顯示窗口相關(guān)的配置。

對(duì)象

可缺省,缺省值見表2。

isDefault

表示該卡片是否為默認(rèn)卡片,每個(gè)UIAbility有且只有一個(gè)默認(rèn)卡片。- true:默認(rèn)卡片。- false:非默認(rèn)卡片。

布爾值


colorMode

表示卡片的主題樣式,取值范圍如下:- auto:跟隨系統(tǒng)的顏色模式值選取主題。- dark:深色主題。- light:淺色主題。

字符串

可缺省,缺省值為“auto”。

supportDimensions

表示卡片支持的外觀規(guī)格,取值范圍:- 1 * 2:表示1行2列的二宮格。- 2 * 2:表示2行2列的四宮格。- 2 * 4:表示2行4列的八宮格。- 4 * 4:表示4行4列的十六宮格。

字符串?dāng)?shù)組


defaultDimension

表示卡片的默認(rèn)外觀規(guī)格,取值必須在該卡片supportDimensions配置的列表中。

字符串


updateEnabled

表示卡片是否支持周期性刷新(包含定時(shí)刷新和定點(diǎn)刷新),取值范圍:- true:表示支持周期性刷新,可以在定時(shí)刷新(updateDuration)和定點(diǎn)刷新(scheduledUpdateTime)兩種方式任選其一,當(dāng)兩者同時(shí)配置時(shí),定時(shí)刷新優(yōu)先生效。- false:表示不支持周期性刷新。

布爾類型


scheduledUpdateTime

表示卡片的定點(diǎn)刷新的時(shí)刻,采用24小時(shí)制,精確到分鐘。> 說明:> updateDuration參數(shù)優(yōu)先級(jí)高于scheduledUpdateTime,兩者同時(shí)配置時(shí),以u(píng)pdateDuration配置的刷新時(shí)間為準(zhǔn)。

字符串

可缺省,缺省時(shí)不進(jìn)行定點(diǎn)刷新。

updateDuration

表示卡片定時(shí)刷新的更新周期,單位為30分鐘,取值為自然數(shù)。當(dāng)取值為0時(shí),表示該參數(shù)不生效。當(dāng)取值為正整數(shù)N時(shí),表示刷新周期為30*N分鐘。> 說明:> updateDuration參數(shù)優(yōu)先級(jí)高于scheduledUpdateTime,兩者同時(shí)配置時(shí),以u(píng)pdateDuration配置的刷新時(shí)間為準(zhǔn)。

數(shù)值

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

formConfigAbility

表示卡片的配置跳轉(zhuǎn)鏈接,采用URI格式。

字符串

可缺省,缺省值為空。

metadata

表示卡片的自定義信息,參考Metadata數(shù)組標(biāo)簽。

對(duì)象

可缺省,缺省值為空。

dataProxyEnabled

表示卡片是否支持卡片代理刷新,取值范圍:- true:表示支持代理刷新。- false:表示不支持代理刷新。設(shè)置為true時(shí),定時(shí)刷新和下次刷新不生效,但不影響定點(diǎn)刷新。

布爾類型

可缺省,缺省值為false。

isDynamic

表示此卡片是否為動(dòng)態(tài)卡片(僅針對(duì)ArkTS卡片生效)。- true:為動(dòng)態(tài)卡片 。- false:為靜態(tài)卡片。

布爾類型

可缺省,缺省值為true。

transparencyEnabled

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

布爾類型

可缺省,缺省值為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助手助你學(xué)妙語.",
      "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"
      ]
    }
  ]
}

3、實(shí)現(xiàn)2*2/2*4/4*4服務(wù)卡片

1-2卡片

首先我們來看1-2卡片的實(shí)現(xiàn)。

@Entry
@Component
struct 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('知識(shí)百科/文本翻譯/...')
          .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": {}
      });
    })
  }
}
效果

實(shí)現(xiàn)效果如圖所示:

原理

我可以用router來進(jìn)行跳轉(zhuǎn),默認(rèn)不傳遞任何參數(shù),就會(huì)跳轉(zhuǎn)到首頁。

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

2-4的卡片

我們來看妙語集這一個(gè)2-4卡片的實(shí)現(xiàn)。

完整代碼
const storage = new LocalStorage();
@Entry(storage)
@Component
struct PoetryCard {
  readonly PAGE_FULL: string = "100%";
  readonly PRE_96: string = "96%";
  readonly SIZE_40: number = 40;
  readonly SIZE_30: number = 30;
  readonly SIZE_20: number = 20;
  readonly SIZE_16: number = 16;
  readonly SIZE_8: number = 8;
  readonly SIZE_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)
  }
}
效果

原理

我們是如何實(shí)現(xiàn)數(shù)據(jù)刷新的呢?

我們首先判斷返回的functionName,如果是refreshing,那么我們就去請求網(wǎng)絡(luò)接口,并完成數(shù)據(jù)的顯示和刷新。具體的關(guān)鍵代碼如下所示。

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

4-4的卡片

完整代碼:

@Entry
@Component
struct HistoryCard {

  readonly PAGE_FULL: string = "100%";
  readonly PRE_96: string = "96%";
  readonly SIZE_81: number = 81;
  readonly SIZE_64: number = 64;
  readonly SIZE_48: number = 48;
  readonly SIZE_32: number = 32;
  readonly SIZE_24: number = 24;
  readonly SIZE_16: number = 16;
  readonly SIZE_8: number = 8;
  readonly SIZE_4: number = 4;
  readonly DEFAULT_AI_APP_LIST: Array<AiAppConfig> = [
    {
      appId: "6548c7fdeb28cf9c75531f66",
      chatId: "",
      name: "知識(shí)百科小助手",
      avatar: "/common/imgs/ic_wiki.svg",
      intro: "知識(shí)百科小助手。"
    },
    {
      appId: "65488134eb28cf9c75530e48",
      chatId: "",
      name: "節(jié)日小助手",
      avatar: "/common/imgs/ic_festival.svg",
      intro: "節(jié)日小助手。"
    },
    {
      appId: "65487d64eb28cf9c75530cd2",
      chatId: "",
      name: "文本翻譯助手",
      avatar: "/common/imgs/ic_document.svg",
      intro: "文本翻譯助手。"
    },
    {
      appId: "654ed429ab7249585cd2cab7",
      chatId: "",
      name: "產(chǎn)品名稱助手",
      avatar: "/common/imgs/ic_product.svg",
      intro: "產(chǎn)品名稱助手。"
    },
    {
      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('查看歷史數(shù)據(jù)')
          .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應(yīng)用配置
 */
interface AiAppConfig {
  appId: string;  // AI應(yīng)用AppId
  chatId: string; // 會(huì)話窗口ID
  name: string; // AI應(yīng)用名稱
  avatar: string; // AI應(yīng)用LOGO
  intro?: string;  // AI應(yīng)用介紹
}

interface ChatHistory {
  chat: AiAppConfig;
  total: number;
}
效果

原理

在卡片中使用postCardAction接口的router能力,能夠快速拉起卡片提供方應(yīng)用的指定UIAbility,因此UIAbility較多的應(yīng)用往往會(huì)通過卡片提供不同的跳轉(zhuǎn)按鈕,實(shí)現(xiàn)一鍵直達(dá)的效果。

通常使用按鈕控件來實(shí)現(xiàn)頁面拉起。

@Entry
@Component
struct WidgetCard {
  build() {
    Column() {
      Button('跳轉(zhuǎn)')
        .onClick(() => {
          console.info('Jump to EntryAbility funA');
          postCardAction(this, {
            action: 'router',
            abilityName: 'EntryAbility', // 只能跳轉(zhuǎn)到當(dāng)前應(yīng)用下的UIAbility
            params: {
              targetPage: 'funA' // 在EntryAbility中處理這個(gè)信息
            }
          });
        })
    }
    .width('100%')
    .height('100%').justifyContent(FlexAlign.SpaceAround)
  }
}
  • 在UIAbility中接收router事件并獲取參數(shù),根據(jù)傳遞的params不同,選擇拉起不同的頁面。
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
import Want from '@ohos.app.ability.Want';
import Base from '@ohos.base';
import AbilityConstant from '@ohos.app.ability.AbilityConstant';

let selectPage: string = "";
let currentWindowStage: window.WindowStage | null = null;

export default class EntryAbility extends UIAbility {
  // 如果UIAbility第一次啟動(dòng),在收到Router事件后會(huì)觸發(fā)onCreate生命周期回調(diào)
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    // 獲取router事件中傳遞的targetPage參數(shù)
    console.info("onCreate want:" + JSON.stringify(want));
    if (want.parameters?.params !== undefined) {
      let params: Record<string, string> = JSON.parse(want.parameters?.params.toString());
      console.info("onCreate router targetPage:" + params.targetPage);
      selectPage = params.targetPage;
    }
  }
  // 如果UIAbility已在后臺(tái)運(yùn)行,在收到Router事件后會(huì)觸發(fā)onNewWant生命周期回調(diào)
  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam) {
    console.info("onNewWant want:" + JSON.stringify(want));
    if (want.parameters?.params !== undefined) {
      let params: 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) {
    let targetPage: string;
    // 根據(jù)傳遞的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;
      }
    });
  }
};

六、總結(jié)

通過蜜蜂AI助手元服務(wù)的開發(fā),我們體驗(yàn)到了端云一體化帶來的便捷,尤其注冊登陸這一塊,有了云端的接入,我們可以很快的加入。另外在項(xiàng)目里我們還用到了低碼能力,不用一行代碼,就完成了手機(jī)號(hào)登陸的功能。

本次鴻蒙和AI的結(jié)合,給了我新的體驗(yàn)。大家也可以自行嘗試下HarmonyOS的開發(fā),會(huì)給你帶來不一樣的體驗(yàn)。

想了解更多關(guān)于開源的內(nèi)容,請?jiān)L問:

51CTO 鴻蒙開發(fā)者社區(qū)

https://ost.51cto.com

責(zé)任編輯:jianghua 來源: 51CTO 鴻蒙開發(fā)者社區(qū)
相關(guān)推薦

2019-04-19 13:37:01

FacebookAI語音助手人工智能

2024-08-12 08:41:40

2024-02-26 00:00:00

AI編程助手Copilot

2025-04-25 08:08:46

2024-09-14 12:42:32

2018-09-27 14:14:36

Infor AI

2024-05-21 12:13:12

2023-08-07 12:53:05

開發(fā)服務(wù)

2023-08-11 14:00:42

鴻蒙元服務(wù)

2023-05-16 09:53:18

ChatGPT人工智能

2024-09-27 17:06:13

2023-11-09 11:31:43

GitHub開發(fā)

2024-07-11 15:26:23

2024-12-12 09:00:33

2024-11-15 16:35:13

2025-04-18 00:00:00

2025-03-28 00:00:00

SOCAI安全

2025-03-28 07:33:09

數(shù)據(jù)庫AI助手設(shè)計(jì)

2025-01-27 08:00:00

2018-10-17 14:27:47

人工智能語音助手AI助手
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)