Open Harmony开发之分布式账本

695 篇文章 17 订阅
39 篇文章 1 订阅

简介

Demo基于Open Harmony系统使用ETS语言进行编写,本Demo主要通过设备认证、分布式拉起、分布式数据管理等功能来实现。

应用效果

  1. 设备认证,获取同一个局域网内的设备ID,并拉起应用

  1. 添加数据并在另一台设备显示该数据

开发步骤

1.新建Openharmony ETS工程

在DevEco Studio中点击File -> New Project ->[Standard]Empty Ability->Next,Language 选择ETS语言,最后点击Finish即创建成功。

2.界面代码编写
首页界面

 build() {
    Flex({ direction: FlexDirection.Column}) {
      //发现设备
      Button('发现设备', { type: ButtonType.Normal, stateEffect: true })
        .borderRadius(8)
        .backgroundColor(0x317aff).width(90)
        .onClick(() =>{
          this.fun()
        })
      //设备认证
      Button('authDevice', { type: ButtonType.Normal, stateEffect: true })
        .borderRadius(8)
        .backgroundColor(0x317aff).width(90)
        .onClick(() =>{
          this.authDevice()
        })

    // 拉起应用
      Button('拉起应用', { type: ButtonType.Normal, stateEffect: true })
        .borderRadius(8)
        .backgroundColor(0x317aff).width(90)
        .onClick(() =>{
                    this.startAb()
        })

      Stack({
        alignContent:Alignment.TopEnd
      }){
        Text('家庭账本')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .margin({left:12})
           .onClick(() =>{
             //          routePage()
             this.fun()
           })
        Image("/picture/add.png")
          .width(40)
          .height(40)
          .align(Alignment.Start)
          .margin({
            right:12
          }).onClick(() =>{
          routePage()
        })

      }
      .width(350)
      .height(60)
      .margin({
        top:10,
        bottom:10
      })
      Flex({
        direction: FlexDirection.Column, alignItems:ItemAlign.Start,
      }){

        Text("2022年12月")
          .fontSize(20)
          .fontColor(Color.White)

        Text("结余")
          .fontSize(20)
          .fontColor(Color.White)
          .margin({
            top:30
          }).align(Alignment.Start)

        Text("总支出0|总收入0")
          .fontSize(16)
          .fontColor(Color.White)
          .margin({
            top:10
          }).align(Alignment.Start)
      }
      .backgroundColor("#665A5A")
      .height(230)
      .layoutWeight(1)
      .padding(10)
      .onClick(() =>{
        routePage()
      })

      Tabs() {
        TabContent() {
          ProjectList()
        }
        .tabBar("项目")

        TabContent() {
          Statistics()
        }
        .tabBar("统计")
      }
    }
    .width('100%')
    .height('100%')
    .padding({
      left:12,right:12
    })

  }

底部TabContent 项目模块

@Component
struct ProjectList {
  remoteDataManager = new RemoteDataManager()
  @State ProjectData: Array<any> = []
  TestData:any[] =  []
  TestKvData: Array<any> = []
  kvManager = null
  kvStore = null
  STORE_ID = 'store_accountbook'
  aboutToAppear(){
    try {
      const config = {
        userInfo: {
          userId: '0',
          userType: 0
        },
        bundleName: 'com.example.accountbookets'
      }
      factory.createKVManager(config).then((manager) => {
        this.kvManager = manager
        let options =
          {
            createIfMissing: true,
            encrypt: false,
            backup: false,
            autoSync: true,
            kvStoreType: 1,
            securityLevel: 3
          }
        this.kvManager.getKVStore(this.STORE_ID, options).then((store) => {
          this.kvStore = store
          this.kvStore.get("key2").then((data) => {
            this.ProjectData = JSON.parse(data)
          })

        }).catch((err) => {
        })

      }).catch((err) => {
      })
    } catch (e) {
    }
  }

  @Builder ProjectItem(image, name, des,time,money) {
    Flex({ direction: FlexDirection.Row,alignItems: ItemAlign.Center }){
      Image($r("app.media.icon1"))
        .width(30)
        .height(30)

      Column() {
        Text(name)
          .fontSize(16)
          .fontColor(Color.Black)
        Text('11:20')
          .fontSize(16)
          .fontColor(Color.Gray)
      }
      .alignItems(HorizontalAlign.Start)
      .margin({left:15})

      Text('HUAWEI')
        .fontSize(12)
        .fontColor(Color.Black)
        .margin({left:20})

      Text(des)
        .fontSize(14)
        .fontColor(Color.Gray)
        .margin({left:15})

      Column() {
        Text('-100')
          .fontSize(16)
          .fontColor(Color.Black)
        Text(time)
          .fontSize(16)
          .fontColor(Color.Gray)
      }
      .alignItems(HorizontalAlign.Start)
      .margin({left:20})
    }
    .width(400)
    .height(50)
    .margin({top:10})

  }

  build() {
    List() {
      ForEach(this.ProjectData, (item) => {
        ListItem() {
          this.ProjectItem(item.image, item.name, item.des,item.time,item.money)
        }
        .onClick(() => {
        })

      }, (item) => JSON.stringify(item)) {

      }


    }
  }
}

底部TabContent 统计模块

@Component
struct Statistics{
  build(){
    Flex({ direction: FlexDirection.Row}){

      Tabs() {
        TabContent() {
          PayList()
        }
        .tabBar("支出分类")

        TabContent() {

        }
        .tabBar("成员分类")
      }
    }
  }
}

统计模块里面的TabContent

@Component
struct PayList {

  private PayData: PayBean[] = initializeOnStartup()
  @Builder PayItem(previewUrl, title, describe) {
    Flex({ direction: FlexDirection.Row,alignItems: ItemAlign.Center }){
      Image(previewUrl)
        .width(30)
        .height(30)


      Text(title)
        .fontSize(16)
        .fontColor(Color.Black)
        .margin({left:8})

      Text('100%')
        .fontSize(12)
        .fontColor(Color.Black)
        .margin({left:8})

      Progress({ value: 20, total: 150, style: ProgressStyle.Linear }).color(Color.Red).value(150).width(200)

      Text('-100')
        .fontSize(14)
        .fontColor(Color.Gray)
        .margin({left:8})
    }
    .width(400)
    .height(50)
    .margin({top:10})

  }

  build() {
    List() {
      ForEach(this.PayData, (item) => {
        ListItem() {
          this.PayItem(item.image, item.name, item.des)
        }
        .onClick(() => {
          console.info("点击我")
          router.push({
            uri: "pages/VideoPlayer",

          })
        })
      }, (item) => JSON.stringify(item)) {

      }
    }
  }
}
2) add.ets页面

build() {
    Flex({ direction: FlexDirection.Column,alignItems: ItemAlign.Center}) {

      Flex({ direction: FlexDirection.Row,alignItems: ItemAlign.Center})
      {
        Image("/picture/icon_back.png")
          .width(35)
          .height(35)
         .onClick(() =>{
           router.push({
             uri: "pages/index",

           })
         })

        Text("加一笔")
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        .margin({left:20})
      }.margin({top:10})
      .padding({left:20})
      .height(100)
      .width(500)
      Stack({
        alignContent: Alignment.TopStart
      }){
        Tabs() {
          TabContent() {
            pay({payTime:$strTime,payRemark:$strRemark,payType:$strType})
          }
          .tabBar("支出")

          TabContent() {
            Income()
          }
          .tabBar("收入")
        }
        .height(450)
      }.width(500)
      .height(500)
      Flex({
        direction: FlexDirection.Row,alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center
      }){
        Text("输入金额")
          .fontColor(Color.Black)
          .fontSize(20)
          .margin({
            right:15
          })
          .width(100)
        TextInput({ placeholder: '100', text:this.strMoney })
          .type(InputType.Normal)
          .placeholderColor(Color.Gray)
          .placeholderFont({ size: 20, weight: 2})
          .enterKeyType(EnterKeyType.Search)
          .caretColor(Color.Green)
          .width(250)
          .height(40)
          .borderRadius('20px')
      }
      .width(400)
      .height(50)

      Text('保存')
        .fontColor(Color.White)
        .fontSize(20)
        .margin({
          top:20
        })
        .textAlign(TextAlign.Center)
      .width(380)
        .height(80)
      .backgroundColor("#FE4F16")
        .onClick(() =>{

          TestData.push({image:"/picture/icon1.png",title:'canyin',des:'ceshi',time:'2021',money:'50'})
          if (AppStorage.Get("key1") == null) {
            AppStorage.SetAndLink("key1", TestData)
            this.remoteDataManager.dataChange("key2", JSON.stringify(TestData))
          }else{
            this.TestStorageData = AppStorage.Get("key1")
            //
//            this.TestStorageData.push({image:"/picture/icon1.png",title:'canyin',des:'beizhu',time:'2021',money:'50'})
            //具体代码
            this.TestStorageData.push({image:"/picture/icon1.png",title:this.strType,des:this.strRemark,time:this.strTime,money:this.strMoney})
            AppStorage.SetAndLink("key1", this.TestStorageData)
            let str = JSON.stringify(this.TestStorageData)
            this.TestKvData = JSON.parse(str)
            this.remoteDataManager.dataChange("key2", JSON.stringify(this.TestKvData))
          }
          router.push({
            uri: "pages/index",

          })
        })

    }
    .width('100%')
    .height('100%')
  }

add页面支出模块

@Component
struct pay{
  @Link payTime:string
  @Link payRemark:string
  @Link payType:string
  @State private index:number = 0
  @State strType:string = "canyin"
  @State AccountData: Array<any> = [
    { previewUrl: "/picture/icon1.png", title: "canyin" ,number:0},
    { previewUrl: "/picture/icon2_2.png", title: "gouwu" ,number:1},
    { previewUrl: "/picture/icon3_3.png", title: "jiaotong" ,number:2},
    { previewUrl: "/picture/icon4_4.png", title: "fuwu" ,number:3},
    { previewUrl: "/picture/icon5_5.png", title: "jiaoyu" ,number:4},
    { previewUrl: "/picture/icon6_6.png", title: "yundong" ,number:5},
    { previewUrl: "/picture/icon7_7.png", title: "luxing" ,number:6},
    { previewUrl: "/picture/icon8_8.png", title: "yiliao" ,number:7},
//    { previewUrl: "/picture/icon9_9.png", title: "生活" ,number:9},
//    { previewUrl: "/picture/icon10_10.png", title: "宠物" ,number:10},
  ]

  @Builder ProItem(previewUrl, title,number) {
    Stack() {
      Flex({
        direction: FlexDirection.Column
      }) {
        if (this.index == number) {
          if (number == 0) {
            Image("/picture/icon1.png")
              .width(60)
              .height(60)
          }else if (number == 1) {
            Image("/picture/icon2.png")
              .width(60)
              .height(60)

          }else if (number == 2) {
            Image("/picture/icon3.png")
              .width(60)
              .height(60)

          }else if (number == 3) {
            Image("/picture/icon4.png")
              .width(60)
              .height(60)
          }else if (number == 4) {
            Image("/picture/icon5.png")
              .width(60)
              .height(60)

          }else if (number == 5) {
            Image("/picture/icon6.png")
              .width(60)
              .height(60)

          }else if (number == 6) {
            Image("/picture/icon7.png")
              .width(60)
              .height(60)
          }else if (number == 7) {
            Image("/picture/icon8.png")
              .width(60)
              .height(60)
          }else if (number == 8) {
            Image("/picture/icon9.png")
              .width(60)
              .height(60)
          }else if (number == 9) {
            Image("/picture/icon10.png")
              .width(60)
              .height(60)
          }
        }else{
          if (number == 0) {
            Image("/picture/icon1_1.png")
              .width(60)
              .height(60)
          }else{
            Image(previewUrl)
              .width(60)
              .height(60)
          }

        }
        Column() {
          Text(title)
            .fontSize(16)
            .fontColor(Color.Black)
        }
        .alignItems(HorizontalAlign.Center)
      }

    }
    .height(100)
    .width(100)
    .margin({
      bottom: 16
    })

  }
  build(){
    Flex({direction: FlexDirection.Column}){

      Grid(){
        ForEach(this.AccountData, (item) => {
          GridItem() {
            this.ProItem(item.previewUrl, item.title,item.number)
          }
          .onClick(() => {
            console.info("点击我")
            this.index = item.number
            this.payType = this.AccountData[this.index].title
          })
        }, (item) => JSON.stringify(item)) {

        }
      }
      .rowsTemplate('1fr 1fr')
      .columnsTemplate('1fr 1fr 1fr 1fr')
      .columnsGap(8)
      .rowsGap(8)
      .height(200)
//      Time()
//      Remark()
      // ******************时间**********************
      Flex({
        direction: FlexDirection.Row,alignItems: ItemAlign.Center
      }){
        Text("时间")
          .fontColor(Color.Black)
          .fontSize(20)
          .margin({
            right:15
          })
          .width(70)

        TextInput({ placeholder: '输入收支时间', text: this.payTime })
          .type(InputType.Normal)
          .placeholderColor(Color.Gray)
          .placeholderFont({ size: 20, weight: 2})
          .enterKeyType(EnterKeyType.Search)
          .caretColor(Color.Green)
          .width(300)
          .height(40)
          .borderRadius('20px')
          .backgroundColor(Color.White)
          .onChange((value: string) => {
            this.payTime = value
          })

      }
      .margin({
        top:20,left:15
      })
      .width(200)
      //*******************备注********************
      Flex({
        direction: FlexDirection.Row,alignItems: ItemAlign.Center
      }){
        Text("备注")
          .fontColor(Color.Black)
          .fontSize(20)
          .margin({
            right:15
          })
          .width(70)

        TextInput({ placeholder: '输入说明', text: this.payRemark })
          .type(InputType.Normal)
          .placeholderColor(Color.Gray)
          .placeholderFont({ size: 20, weight: 2})
          .enterKeyType(EnterKeyType.Search)
          .caretColor(Color.Green)
        //        .layoutWeight(8)
          .height(40)
          .width(300)
          .borderRadius('20px')
          .backgroundColor(Color.White)
          .onChange((value: string) => {
            this.payRemark = value
          })
      }
      .margin({
        top:20,left:15
      })
      .width(50)
      .height(50)
    }
    .height('100%')
    .layoutWeight(1)

  }
}

收入模块代码

@Component
struct Income{

  build(){
    Flex({direction: FlexDirection.Column}){
      Time()
      Remark()

    }
  }
}

时间模块

@Component
struct Time{
  @State inputTime:string = ''
  build(){
    Flex({
      direction: FlexDirection.Row,alignItems: ItemAlign.Center
    }){
      Text("时间")
        .fontColor(Color.Black)
        .fontSize(20)
        .margin({
          right:15
        })
      .width(70)

      TextInput({ placeholder: '2021', text: this.inputTime })
        .type(InputType.Normal)
        .placeholderColor(Color.Gray)
        .placeholderFont({ size: 20, weight: 2})
        .enterKeyType(EnterKeyType.Search)
        .caretColor(Color.Green)
        .width(300)
        .height(40)
        .borderRadius('20px')
        .backgroundColor(Color.White)
    }
    .margin({
      top:20,left:15
    })
    .width(200)
  }
}

备注模块

@Component
struct Remark{
  @State inputRemark:string = ''
  build(){
    Flex({
      direction: FlexDirection.Row,alignItems: ItemAlign.Center
    }){
      Text("备注")
        .fontColor(Color.Black)
        .fontSize(20)
        .margin({
          right:15
        })
      .width(70)

      TextInput({ placeholder: 'ceshe', text: this.inputRemark })
        .type(InputType.Normal)
        .placeholderColor(Color.Gray)
        .placeholderFont({ size: 20, weight: 2})
        .enterKeyType(EnterKeyType.Search)
        .caretColor(Color.Green)
//        .layoutWeight(8)
        .height(40)
        .width(300)
        .borderRadius('20px')
        .backgroundColor(Color.White)

    }
    .margin({
      top:20,left:15
    })
    .width(50)
    .height(50)

  }
}
3.设备认证

设备认证是依赖 DeviceManager 组件来实现的,详细代码参考源码RemoteDeviceModel.ets

1.创建DeviceManager实例

 registerDeviceListCallback(callback) {
    if (typeof (this.#deviceManager) === 'undefined') {
      deviceManager.createDeviceManager('com.example.tictactoegame', (error, value) => {
        if (error) return
        this.#deviceManager = value;
        this.registerDeviceListCallback_(callback);
      });
    } else {
      this.registerDeviceListCallback_(callback);
    }
  }

2.查询可信设备列表

 var list = this.#deviceManager.getTrustedDeviceListSync();
    if (typeof (list) != 'undefined' && typeof (list.length) != 'undefined') {
      this.deviceList = list;
    }

3.注册设备上下线监听

this.#deviceManager.on('deviceStateChange', (data) => {
      switch (data.action) {
        case 0:
          this.deviceList[this.deviceList.length] = data.device;
          this.callback();
          if (this.authCallback != null) {
            this.authCallback();
            this.authCallback = null;
          }
          break;
        case 2:
          if (this.deviceList.length > 0) {
            for (var i = 0; i < this.deviceList.length; i++) {
              if (this.deviceList[i].deviceId === data.device.deviceId) {
                this.deviceList[i] = data.device;
                break;
              }
            }
          }
          this.callback();
          break;
        case 1:
          if (this.deviceList.length > 0) {
            var list = [];
            for (var i = 0; i < this.deviceList.length; i++) {
              if (this.deviceList[i].deviceId != data.device.deviceId) {
                list[i] = data.device;
              }
            }
            this.deviceList = list;
          }
          this.callback();
          break;
        default:
          break;
      }
    });

4.设备发现

this.#deviceManager.on('deviceFound', (data) => {
      for (let i = 0; i < this.discoverList.length; i++) {
        if (that.discoverList[i].deviceId === data.device.deviceId) {
          return;
        }
      }
      this.discoverList[this.discoverList.length] = data.device;
      this.callback();
    });

5.设备认证

authDevice(deviceInfo, callback){
    let extraInfo = {
      "targetPkgName": 'com.example.tictactoegame',
      "appName": 'com.example.tictactoegame',
      "appDescription": 'com.example.tictactoegame',
      "business": '0'
    };
    let authParam = {
      "authType": 1,
      "appIcon": '',
      "appThumbnail": '',
      "extraInfo": extraInfo
    };
    this.#deviceManager.authenticateDevice(deviceInfo, authParam, (err, data) => {
      if (err) {
        this.authCallback = null;
      } else {
        this.authCallback = callback;
      }
    });
  }
4.数据管理

分布式数据管理 依赖@ohos.data.distributedData模块实现,详细参考源码RemoteDataManager.ets

1.导入该模块

import factory from '@ohos.data.distributedData';

2.创建KVManager实例,用于管理数据库对象

  registerDataListCallback(callback) {
    let that = this
    if (this.kvManager == null) {
      try {
        const config = {
          userInfo: {
            userId: '0',
            userType: 0
          },
          bundleName: 'com.example.tictactoegame'
        }
        factory.createKVManager(config).then((manager) => {
          that.kvManager = manager
          that.registerDataListCallback_(callback)
        }).catch((err) => {
        })
      } catch (e) {
      }
    } else {
      this.registerDataListCallback_(callback)
    }
  }

3.创建并获取KVStore数据库

registerDataListCallback_(callback) {
    let that = this
    if (that.kvManager == null) {
      callback()
      return
    }
    if (that.kvStore == null) {
      try {
        let options =
          {
            createIfMissing: true,
            encrypt: false,
            backup: false,
            autoSync: true,
            kvStoreType: 1,
            securityLevel: 3
          }
        this.kvManager.getKVStore(this.STORE_ID, options).then((store) => {
          that.kvStore = store
          that._registerDataListCallback_(callback)
        }).catch((err) => {
        })
      } catch (e) {
      }
    } else {
      this._registerDataListCallback_(callback)
    }
  }

4.订阅指定类型的数据变更通知

  _registerDataListCallback_(callback) {
    let that = this
    if (that.kvManager == null) {
      callback()
      return
    }
    this.kvStore.on('dataChange', 1, function(data) {
      if (data) {
         that.arr = data.updateEntries
        callback()
      }
    })
  }

5.添加指定类型键值对到数据库

  startAbilityContinuation(deviceId) {
    let wantValue = {
      bundleName: 'com.example.tictactoegame',
      abilityName: 'com.example.tictactoegame.MainAbility',
      deviceId: deviceId,
      parameters: {
        uri: 'pages/Fight'
      }
    };
    featureAbility.startAbility({ want: wantValue }).then(() => {
      router.replace({ uri: 'pages/Fight' })
    });
  }
5.远程拉起设备app

使用 FeatureAbility 模块的startAbility接口拉起远程设备app

  startAbilityContinuation(deviceId) {
    let wantValue = {
      bundleName: 'com.example.tictactoegame',
      abilityName: 'com.example.tictactoegame.MainAbility',
      deviceId: deviceId,
      parameters: {
        uri: 'pages/Fight'
      }
    };
    featureAbility.startAbility({ want: wantValue }).then(() => {
      router.replace({ uri: 'pages/Fight' })
    });
  }
6.添加数据

新建一个账单数据 添加到分布式数据

this.remoteDataManager.dataChange("key2", JSON.stringify(this.TestKvData))

在另一台设备监听并获取显示该条数据

 private onPageShow() {

    this.remoteDataManager.registerDataListCallback(() => {
      let arr = this.remoteDataManager.arr[0]
      this.strTest = arr.value.value
      this.ProjectData = JSON.parse(this.strTest)
     
  }

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:https://qr21.cn/FV7h05

入门必看:https://qr21.cn/FV7h05
1.  应用开发导读(ArkTS)
2.  ……

HarmonyOS 概念:https://qr21.cn/FV7h05

  1. 系统定义
  2. 技术架构
  3. 技术特性
  4. 系统安全

如何快速入门:https://qr21.cn/FV7h05
1.  基本概念
2.  构建第一个ArkTS应用
3.  ……

开发基础知识:https://qr21.cn/FV7h05
1.  应用基础知识
2.  配置文件
3.  应用数据管理
4.  应用安全管理
5.  应用隐私保护
6.  三方应用调用管控机制
7.  资源分类与访问
8.  学习ArkTS语言
9.  ……

基于ArkTS 开发:https://qr21.cn/FV7h05
1.  Ability开发
2.  UI开发
3.  公共事件与通知
4.  窗口管理
5.  媒体
6.  安全
7.  网络与链接
8.  电话服务
9.  数据管理
10.  后台任务(Background Task)管理
11.  设备管理
12.  设备使用信息统计
13.  DFX
14.  国际化开发
15.  折叠屏系列
16.  ……

鸿蒙开发面试真题(含参考答案):https://qr21.cn/FV7h05

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值