ToB企服应用市场:ToB评测及商务社交产业平台

标题: 鸿蒙【云端一体化】 [打印本页]

作者: 大连全瓷种植牙齿制作中心    时间: 2024-10-2 09:24
标题: 鸿蒙【云端一体化】








开发云函数调用云函数

开发云函数

新建项目和应用,开通云函数服务(AGC)






开通云函数服务




使用端云一体化模板创建应用 (DevEco Studio)




新建云函数(DevEcoStudio) 】


编写云函数代码(DevEco Studio)


部署云函数(DevEco Studio)


设置和测试(AGC)






端侧调用云函数

添加依赖


初始化AGConnect




ArkTS 界面开发


ArkTS 调用云函数

云函数,要在真机大概模拟器才气用

云函数开发细节

传参问题

端侧输入姓名,云测根据传来的参数,进行处理,告诉端侧显示不同的欢迎词
端侧
云测
我们要知道端侧传过来什么,就要知道event对象里有什么
查文档







为了步伐结实,我们可以把先判定有没有body,没有怎么处理,大概捕获异常
环境变量

如果我有一些设置项,那么他们容易改变,那我不应该把它写死在步伐代码之中,而是把这些设置项呢,给他以环境变量的方式提供
如许就能保证设置项将来改动了,代码不用动
env系统提供的和我们将来可以自界说环境变量


你可以点击这个新增变量来输入变量名变量值
一定要点击保存它才气生效啊
云函数如果它重新部署的话
那么这个上次设置的环境变量会被清空
所以呢我们先去改动一下云函数的代码,把它重新部署之后,然后咱们回过头来再去设置这个环境变量好

流量管理

负载均衡

云函数的一个利益就是它是按量计费,如果你对这个原函数啊并没有使用,那它是不收费的
云函数运行之前,它会把它部署到一个虚拟机实例上
我已经有一段时间没有去调用我的hello,他就把我谁人实例销毁了这个时间是不计费的
那将来我对这个原函数一旦要产生调用,它才会动态的创建一个实例,并且把原函数部署上去,这时间才会产生费用啊
如果这个并发量比较大,那云函数这边它是如何去分配,请求到这每个实例的
这就涉及到了负载均衡





重试

云函数的调用是有可能超时的

那么如果你不希望这个函数执行一次就失败,你还想救一下它
我们可以通过流量管理中有一个器重的功能,在函数超时失败之后呢,可以重新发起一次对云函数的调用,如果这次调用能成功,那我们就不用返回失败的信息,还是可以返回成功的信息啊

重试功能呢其实它也分了三种策略
第一种重策略叫做zero
它的意思呢就是说一旦函数发现了它调用失败,那我就立刻发起器重,它中间不会等待
第二种从事策略叫做constant
当你的函数调用失败了之后,他不会说立刻去发起这个重试,他要隔断两秒之后再发起重试
第三种从策略呢叫做jittered
它其实跟我们前面第二种啊比较像,
都是在每次重试之间会有一个时间隔断,不外它的时间隔断不像我们constant,它的时间隔断是一个常量的时间值,它是一个变化的时间值
每次这个时间隔断是以指数方式增长的啊
zero


constant


jittered

熔断

就跟我们一样平常生存中的保险丝的作用有点像,保险丝呢它在大电流来了以后,保险丝是不是就烧断了
流量管理中的熔断,它的目的也是类似的,比如说我的云函数,它的调用很多次都出错了,那我继续提供原函数服务已经没有意义了
这时间呢我就接纳熔断,熔断一旦发生,那么原函数呢就暂停对外的服务了,也就是这个原函数不可用了,
但是原函数不可用是避免了更大
比如说像雪崩事故的发生好
这是熔断的目的啊

它的设置项其实就这么三项
刚开始这个开关没有开的时间,那函数呢在任意时候都可以对外提供服务
如果我们把这个熔断开关打开,那它就会在满足了某些条件之后触发熔断,一旦函数熔断发生,那么这个函数就不能对外提供服务了

认证服务

开通认证服务






使用认证组件进行认证



第一步登录页面

第二步,登录成功后


至此整个登录流程跑通了

认证流程(1) -上报认证数据

认证凭据,根据你的认证方式不同而不同
比如说呢我们之前用的是手机认证
那手机验证的话,当我点击登录按钮之后,是不是让你输入手机号和验证码,那这个手机号和验证码
就是我们手机认证方式下它的认证凭据
那如果你接纳的是邮箱认证呢
那这个认证据就是你的邮件地址和
邮件里收到的认证码

第一次使用认证服务那服务器这边是不是应该还没有这个用户信息
服务器就创建你的服务信息

认证流程(2) -获取用户信息




自行实现登录需求操纵



  1. @Entry
  2. @Component
  3. struct MyLoginCustom {
  4.   build() {
  5.     Row() {
  6.       Column({ space: 10 }) {
  7.         Text('登录')
  8.           .fontSize(40)
  9.           .fontWeight(FontWeight.Bold)
  10.         Divider()
  11.         TextInput({ placeholder: '请输入手机号' })
  12.           .width('80%')
  13.           .type(InputType.Number)
  14.         Row({ space: 10 }) {
  15.           TextInput({ placeholder: '请输入验证码' })
  16.             .width('55%')
  17.             .type(InputType.Number)
  18.           Button('获取验证码')
  19.         }
  20.         .width('80%')
  21.         .justifyContent(FlexAlign.SpaceBetween)
  22.         Button('登录')
  23.       }
  24.       .width('100%')
  25.     }
  26.     .height('100%')
  27.   }
  28. }
复制代码
自行实现登录倒计时开端实现、


  1. @Entry
  2. @Component
  3. struct MyLoginCustom {
  4.   @State countDown: number = 10
  5.   intervalId: number = 0
  6.   @State verifyCodeButtonText: string = '获取验证码'
  7.   build() {
  8.     Row() {
  9.       Column({ space: 10 }) {
  10.         Text('登录')
  11.           .fontSize(40)
  12.           .fontWeight(FontWeight.Bold)
  13.         Divider()
  14.         TextInput({ placeholder: '请输入手机号' })
  15.           .width('80%')
  16.           .type(InputType.Number)
  17.         Row({ space: 10 }) {
  18.           TextInput({ placeholder: '请输入验证码' })
  19.             .width('55%')
  20.             .type(InputType.Number)
  21.           Button(this.verifyCodeButtonText)
  22.             .onClick(()=> {
  23.               this.intervalId = setInterval(() => {
  24.                 this.verifyCodeButtonText = `${this.countDown} s`
  25.                 if(this.countDown<0){
  26.                   clearInterval(this.intervalId)
  27.                   this.countDown = 10
  28.                   this.intervalId = 0
  29.                   this.verifyCodeButtonText = '获取验证码'
  30.                   return
  31.                 }
  32.                 this.countDown--
  33.               },1000)
  34.             })
  35.         }
  36.         .width('80%')
  37.         .justifyContent(FlexAlign.SpaceBetween)
  38.         Button('登录')
  39.       }
  40.       .width('100%')
  41.     }
  42.     .height('100%')
  43.   }
  44. }
复制代码
自行实现登录倒计时细节调解

  1. @Entry
  2. @Component
  3. struct MyLoginCustom {
  4.   @State countDown: number = 10
  5.   intervalId: number = 0
  6.   @State verifyCodeButtonText: string = '获取验证码'
  7.   @State verifyCodeButtonEnable: boolean = true
  8.   build() {
  9.     Row() {
  10.       Column({ space: 10 }) {
  11.         Text('登录')
  12.           .fontSize(40)
  13.           .fontWeight(FontWeight.Bold)
  14.         Divider()
  15.         TextInput({ placeholder: '请输入手机号' })
  16.           .width('80%')
  17.           .type(InputType.Number)
  18.         Row({ space: 10 }) {
  19.           TextInput({ placeholder: '请输入验证码' })
  20.             .width('55%')
  21.             .type(InputType.Number)
  22.           Button(this.verifyCodeButtonText)
  23.             .enabled(this.verifyCodeButtonEnable)  // 当点击了,之后按钮不可用
  24.             .onClick(()=> {
  25.               this.verifyCodeButtonText = `${this.countDown} s`
  26.               this.countDown--
  27.               this.verifyCodeButtonEnable = false
  28.               this.intervalId = setInterval(() => {
  29.                 this.verifyCodeButtonText = `${this
  30.                 if(this.countDown<0){
  31.                   clearInterval(this.intervalId)
  32.                   this.countDown = 10
  33.                   this.intervalId = 0
  34.                   this.verifyCodeButtonText = '获取验证码'
  35.                   this.verifyCodeButtonEnable = true  // 倒计时结束之后就可用了
  36.                   return
  37.                 }
  38.                 this.countDown--
  39.               },1000)
  40.             })
  41.         }
  42.         .width('80%')
  43.         .justifyContent(FlexAlign.SpaceBetween)
  44.         Button('登录')
  45.       }
  46.       .width('100%')
  47.     }
  48.     .height('100%')
  49.   }
  50. }
复制代码
封装成函数
  1. @Entry
  2. @Component
  3. struct MyLoginCustom {
  4.   @State countDown: number = 10
  5.   intervalId: number = 0
  6.   @State verifyCodeButtonText: string = '获取验证码'
  7.   @State verifyCodeButtonEnable: boolean = true
  8.   waiting() {
  9.     this.verifyCodeButtonText = `${this.countDown} s`
  10.        this.countDown--
  11.        this.verifyCodeButtonEnable = false
  12.        this.intervalId = setInterval(() => {
  13.          this.verifyCodeButtonText = `${this
  14.          if(this.countDown<0){
  15.            clearInterval(this.intervalId)
  16.            this.countDown = 10
  17.            this.intervalId = 0
  18.            this.verifyCodeButtonText = '获取验证码'
  19.            this.verifyCodeButtonEnable = true  // 倒计时结束之后就可用了
  20.            return
  21.          }
  22.          this.countDown--
  23.        },1000)
  24.   }
  25.   build() {
  26.     Row() {
  27.       Column({ space: 10 }) {
  28.         Text('登录')
  29.           .fontSize(40)
  30.           .fontWeight(FontWeight.Bold)
  31.         Divider()
  32.         TextInput({ placeholder: '请输入手机号' })
  33.           .width('80%')
  34.           .type(InputType.Number)
  35.         Row({ space: 10 }) {
  36.           TextInput({ placeholder: '请输入验证码' })
  37.             .width('55%')
  38.             .type(InputType.Number)
  39.           Button(this.verifyCodeButtonText)
  40.             .enabled(this.verifyCodeButtonEnable)  // 当点击了,之后按钮不可用
  41.             .onClick(()=> {
  42.               this.waiting()
  43.             })
  44.         }
  45.         .width('80%')
  46.         .justifyContent(FlexAlign.SpaceBetween)
  47.         Button('登录')
  48.       }
  49.       .width('100%')
  50.     }
  51.     .height('100%')
  52.   }
  53. }
复制代码
自行实现登录发送验证码



  1. import cloud, { VerifyCodeAction } from '@hw-agconnect/cloud'
  2. import hilog from '@ohos.hilog'
  3. import router from '@ohos.router'
  4. @Entry
  5. @Component
  6. struct MyLoginCustom {
  7.   @State countDown: number = 10
  8.   intervalId: number = 0
  9.   @State verifyCodeButtonText: string = '获取验证码'
  10.   @State verifyCodeButtonEnable: boolean = true
  11.   @State phoneNumber: string = ''
  12.   waiting() {
  13.     this.verifyCodeButtonText = `${this.countDown} s`
  14.        this.countDown--
  15.        this.verifyCodeButtonEnable = false
  16.        this.intervalId = setInterval(() => {
  17.          this.verifyCodeButtonText = `${this
  18.          if(this.countDown<0){
  19.            clearInterval(this.intervalId)
  20.            this.countDown = 10
  21.            this.intervalId = 0
  22.            this.verifyCodeButtonText = '获取验证码'
  23.            this.verifyCodeButtonEnable = true  // 倒计时结束之后就可用了
  24.            return
  25.          }
  26.          this.countDown--
  27.        },1000)
  28.   }
  29.   build() {
  30.     Row() {
  31.       Column({ space: 10 }) {
  32.         Text('登录')
  33.           .fontSize(40)
  34.           .fontWeight(FontWeight.Bold)
  35.         Divider()
  36.         TextInput({ placeholder: '请输入手机号' })
  37.           .width('80%')
  38.           .type(InputType.Number)
  39.         Row({ space: 10 }) {
  40.           TextInput({ placeholder: '请输入验证码' })
  41.             .width('55%')
  42.             .type(InputType.Number)
  43.             .onChange(value => {
  44.               this.phoneNumber = value
  45.               })
  46.           Button(this.verifyCodeButtonText)
  47.             .enabled(this.verifyCodeButtonEnable)  // 当点击了,之后按钮不可用
  48.             .onClick(async ()=> {
  49.               this.waiting()
  50.               try {
  51.                  await cloud.auth().requestVerifyCode({
  52.                    verifyCodeType: {
  53.                      kind: 'phone',
  54.                      phoneNumber: this.phoneNumber,
  55.                      countryCode: '86'
  56.                    },
  57.                    action: VerifyCodeAction.REGISTER_LOGIN,
  58.                    lang: 'zh_CN',
  59.                    sendInterval: 10
  60.                  })
  61.                  hilog.info(0, 'VerifyCode', 'Success')
  62.                } catch (e) {
  63.                  // 弹窗提示错误
  64.                  AlertDialog.show({ title: '错误', message: '验证码发送失败' })
  65.                  hilog.error(0, 'VerifyCode', JSON.stringify(e))
  66.                }
  67.             })
  68.         }
  69.         .width('80%')
  70.         .justifyContent(FlexAlign.SpaceBetween)
  71.         Button('登录')
  72.       }
  73.       .width('100%')
  74.     }
  75.     .height('100%')
  76.   }
  77. }
复制代码
自行实现登录登录



  1. import cloud, { VerifyCodeAction } from '@hw-agconnect/cloud'
  2. import hilog from '@ohos.hilog'
  3. import router from '@ohos.router'
  4. @Entry
  5. @Component
  6. struct MyLoginCustom {
  7.   @State countDown: number = 10
  8.   intervalId: number = 0
  9.   @State verifyCodeButtonText: string = '获取验证码'
  10.   @State verifyCodeButtonEnable: boolean = true
  11.   @State phoneNumber: string = ''
  12.   @State verifyCode: string = ''
  13.   waiting() {
  14.     this.verifyCodeButtonText = `${this.countDown} s`
  15.        this.countDown--
  16.        this.verifyCodeButtonEnable = false
  17.        this.intervalId = setInterval(() => {
  18.          this.verifyCodeButtonText = `${this
  19.          if(this.countDown<0){
  20.            clearInterval(this.intervalId)
  21.            this.countDown = 10
  22.            this.intervalId = 0
  23.            this.verifyCodeButtonText = '获取验证码'
  24.            this.verifyCodeButtonEnable = true  // 倒计时结束之后就可用了
  25.            return
  26.          }
  27.          this.countDown--
  28.        },1000)
  29.   }
  30.   build() {
  31.     Row() {
  32.       Column({ space: 10 }) {
  33.         Text('登录')
  34.           .fontSize(40)
  35.           .fontWeight(FontWeight.Bold)
  36.         Divider()
  37.         TextInput({ placeholder: '请输入手机号' })
  38.           .width('80%')
  39.           .type(InputType.Number)
  40.         Row({ space: 10 }) {
  41.           TextInput({ placeholder: '请输入验证码' })
  42.             .width('55%')
  43.             .type(InputType.Number)
  44.             .onChange(value => {
  45.               this.phoneNumber = value
  46.               })
  47.           Button(this.verifyCodeButtonText)
  48.             .enabled(this.verifyCodeButtonEnable)  // 当点击了,之后按钮不可用
  49.             .onClick(async ()=> {
  50.               this.waiting()
  51.               try {
  52.                  await cloud.auth().requestVerifyCode({
  53.                    verifyCodeType: {
  54.                      kind: 'phone',
  55.                      phoneNumber: this.phoneNumber,
  56.                      countryCode: '86'
  57.                    },
  58.                    action: VerifyCodeAction.REGISTER_LOGIN,
  59.                    lang: 'zh_CN',
  60.                    sendInterval: 10
  61.                  })
  62.                  hilog.info(0, 'VerifyCode', 'Success')
  63.                } catch (e) {
  64.                  // 弹窗提示错误
  65.                  AlertDialog.show({ title: '错误', message: '验证码发送失败' })
  66.                  hilog.error(0, 'VerifyCode', JSON.stringify(e))
  67.                }
  68.             })
  69.         }
  70.         .width('80%')
  71.         .justifyContent(FlexAlign.SpaceBetween)
  72.         Button('登录')
  73.          .onClick(async ()=> {
  74.               try {
  75.                   const result = await cloud.auth().signIn({
  76.                   credentialInfo: {
  77.                     kind: 'phone',
  78.                     countryCode: '86',
  79.                     phoneNumber: this.phoneNumber,
  80.                     verifyCode: this.verifyCode
  81.                   }
  82.                 })
  83.                 const user = result.getUser()
  84.                 hilog.info(0, 'Login', 'Success')
  85.                 router.replaceUrl({ url: 'pages/MyWelcome' })  // 跳转到登录页
  86.                } catch (e) {
  87.                  // 弹窗提示错误
  88.                  AlertDialog.show({ title: '错误', message: '验证码发送失败' })
  89.                  hilog.error(0, 'VerifyCode', JSON.stringify(e))
  90.                }
  91.             })
  92.       }
  93.       .width('100%')
  94.     }
  95.     .height('100%')
  96.   }
  97. }
复制代码
自行实现登录登录细节调解

检查用户输入的是否是手机号
  1. import cloud, { VerifyCodeAction } from '@hw-agconnect/cloud'
  2. import hilog from '@ohos.hilog'
  3. import router from '@ohos.router'
  4. @Entry
  5. @Component
  6. struct MyLoginCustom {
  7.   @State countDown: number = 10
  8.   intervalId: number = 0
  9.   @State verifyCodeButtonText: string = '获取验证码'
  10.   @State verifyCodeButtonEnable: boolean = true
  11.   @State phoneNumber: string = ''
  12.   @State verifyCode: string = ''
  13.   waiting() {
  14.     this.verifyCodeButtonText = `${this.countDown} s`
  15.        this.countDown--
  16.        this.verifyCodeButtonEnable = false
  17.        this.intervalId = setInterval(() => {
  18.          this.verifyCodeButtonText = `${this
  19.          if(this.countDown<0){
  20.            clearInterval(this.intervalId)
  21.            this.countDown = 10
  22.            this.intervalId = 0
  23.            this.verifyCodeButtonText = '获取验证码'
  24.            this.verifyCodeButtonEnable = true  // 倒计时结束之后就可用了
  25.            return
  26.          }
  27.          this.countDown--
  28.        },1000)
  29.   }
  30.     async login() {
  31.     try {
  32.       const result = await cloud.auth().signIn({
  33.         credentialInfo: {
  34.           kind: 'phone',
  35.           countryCode: '86',
  36.           phoneNumber: this.phoneNumber,
  37.           verifyCode: this.verifyCode
  38.         }
  39.       })
  40.       const user = result.getUser()
  41.       AppStorage.SetOrCreate('user', user) // 存
  42.       hilog.info(0, 'Login', 'Success')
  43.       router.replaceUrl({ url: this.mainPage })
  44.     } catch (e) {
  45.       hilog.error(0, 'Login', JSON.stringify(e))
  46.       AlertDialog.show({ title: '错误', message: `登录失败 ${JSON.stringify(e)}` })
  47.     }
  48.   }
  49.   async sending() {
  50.     try {
  51.       await cloud.auth().requestVerifyCode({
  52.         verifyCodeType: {
  53.           kind: 'phone',
  54.           phoneNumber: this.phoneNumber,
  55.           countryCode: '86'
  56.         },
  57.         action: VerifyCodeAction.REGISTER_LOGIN,
  58.         lang: 'zh_CN',
  59.         sendInterval: 10
  60.       })
  61.       hilog.info(0, 'VerifyCode', 'Success')
  62.     } catch (e) {
  63.       AlertDialog.show({ title: '错误', message: '验证码发送失败' })
  64.       hilog.error(0, 'VerifyCode', JSON.stringify(e))
  65.     }
  66.   }
  67.   build() {
  68.     Row() {
  69.       Column({ space: 10 }) {
  70.         Text('登录')
  71.           .fontSize(40)
  72.           .fontWeight(FontWeight.Bold)
  73.         Divider()
  74.         TextInput({ placeholder: '请输入手机号' })
  75.           .width('80%')
  76.           .type(InputType.Number)
  77.         Row({ space: 10 }) {
  78.           TextInput({ placeholder: '请输入验证码' })
  79.             .width('55%')
  80.             .type(InputType.Number)
  81.             .onChange(value => {
  82.               this.phoneNumber = value
  83.               })
  84.           Button(this.verifyCodeButtonText)
  85.             .enabled(this.verifyCodeButtonEnable)  // 当点击了,之后按钮不可用
  86.             .onClick(async ()=> {
  87.               this.waiting()
  88.               this.sending()
  89.               
  90.             })
  91.         }
  92.         .width('80%')
  93.         .justifyContent(FlexAlign.SpaceBetween)
  94.         Button('登录')
  95.         // 判断是否是合法的手机号,验证码
  96.         .enabled(this.phoneNumber.length === 11 && this.verifyCode.length === 6)
  97.          .onClick(async ()=> {
  98.               this.login()
  99.             })
  100.       }
  101.       .width('100%')
  102.     }
  103.     .height('100%')
  104.   }
  105. }
复制代码
个人设置页-登出




个人设置页修改昵称和头像

不用户信息存起来

取出头像信息

云存储

上传头像

开通云存储服务



图片缓存问题



工程模式

进入工程模式





用户授权

想使用云存储必要下面三步
1、开通云存储服务
2、更新项目的一个AGC的设置文件
3、用户授权,允许读取媒体文件(前面为什么没有,授权呢?因为我们使用的是端云一体化模板,设置都写好了)
下面我们就看看,模板帮我们写了那些设置


上传进度

问题4:上传进度实现
1.上传状态变量
2…上传进度文字变量、对应的Text
3.编写进度回调

  1. import cloud, { AuthUser } from '@hw-agconnect/cloud'
  2. import router from '@ohos.router'
  3. import hilog from '@ohos.hilog'
  4. import picker from '@ohos.file.picker'
  5. @Entry
  6. @Component
  7. struct MyIndex {
  8.   @State photoUrl: string = ''
  9.   @State displayName: string = ''
  10.   @StorageLink('user') user: AuthUser = null
  11.   @State uploading: boolean = false
  12.   @State uploadingText: string = '0%'
  13.   aboutToAppear() {
  14.     // 1. cloud.auth().getCurrentUser()
  15.     // 2. AppStorage
  16.     this.displayName = this.user?.getDisplayName()
  17.     this.photoUrl = this.user?.getPhotoUrl()
  18.   }
  19.   build() {
  20.     Row() {
  21.       Column({ space: 10 }) {
  22.         Stack() { // 堆叠的效果
  23.           Image(this.photoUrl ? this.photoUrl : $r('app.media.user_dark'))
  24.             .width(70)
  25.             .height(70)
  26.             .borderRadius(70)
  27.             .enabled(!this.uploading)
  28.             .onComplete(()=>{
  29.               this.uploading = false
  30.             })
  31.             .onClick(async () => {
  32.               // this.photoUrl = 'https://img.zcool.cn/community/01a6095f110b9fa8012066219b67d4.png@1280w_1l_2o_100sh.png'
  33.               try {
  34.                 // 1. 从相簿中选照片
  35.                 const options = new picker.PhotoSelectOptions()
  36.                 options.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
  37.                 options.maxSelectNumber = 1
  38.                 const result = await new picker.PhotoViewPicker().select(options)
  39.                 hilog.info(0, 'Upload', `Picker Success ${result.photoUris[0]}`)
  40.                 this.uploading = true
  41.                 // 2. 调云存储 api 上传照片
  42.                 await cloud.storage().upload({
  43.                   localPath: result.photoUris[0],
  44.                   cloudPath: `test/${this.user.getUid()}.jpg`,
  45.                   onUploadProgress: event => {
  46.                     const percent = Math.floor(100 * event.loaded / event.total)
  47.                     this.uploadingText = `${percent}%`
  48.                   }
  49.                 })
  50.                 hilog.info(0, 'Upload', 'Upload Success')
  51.                 // 3. 获取上传照片的网络地址
  52.                 const url = await cloud.storage().getDownloadURL(`test/${this.user.getUid()}.jpg`)
  53.                 this.photoUrl = `${url}&ts=${new Date().getTime()}`
  54.                 // this.uploading = false
  55.                 hilog.info(0, 'Upload', `url: ${url}`)
  56.               } catch (e) {
  57.                 hilog.error(0, 'Upload', JSON.stringify(e))
  58.               }
  59.             })
  60.           if (this.uploading) {
  61.             // 显示上传进度
  62.             Text(this.uploadingText)
  63.               .width(70)
  64.               .height(70)
  65.               .borderRadius(70)
  66.               .fontColor('white')
  67.               .backgroundColor('black')
  68.               .opacity(0.6)
  69.               .fontSize(24)
  70.               .fontWeight(FontWeight.Bolder)
  71.               .textAlign(TextAlign.Center)
  72.           }
  73.         }
  74.         TextInput({ placeholder: '请设置昵称', text: this.displayName })
  75.           .width('50%')
  76.           .onChange(value => {
  77.             this.displayName = value
  78.           })
  79.         Button(`保存`)
  80.           .onClick(async () => {
  81.             try {
  82.               await this.user.updateProfile({
  83.                 displayName: this.displayName,
  84.                 photoUrl: this.photoUrl
  85.               })
  86.               hilog.info(0, 'updateProfile', 'Success')
  87.             } catch (e) {
  88.               hilog.error(0, 'updateProfile', JSON.stringify(e))
  89.             }
  90.           })
  91.         Button(`登出`)
  92.           .onClick(async () => {
  93.             try {
  94.               await cloud.auth().signOut()
  95.               hilog.info(0, 'SignOut', 'Success')
  96.               router.replaceUrl({ url: 'pages/MyLoginCustom' })
  97.             } catch (e) {
  98.               hilog.error(0, 'SignOut', JSON.stringify(e))
  99.             }
  100.           })
  101.       }
  102.       .width('100%')
  103.     }
  104.     .height('100%')
  105.   }
  106. }
复制代码
在历程中直接竣事步伐(没有点击退出),再次点击登录,会报错


原因,我们虽然竣事了步伐,由于我们没有正常退出步伐,用户会话还在,再次登录的时间出现错误

云数据库

概念

存储区,对象类型,对象



数据类型

数据类型取值范围分析排序方式String最大长度200如果字符串长度凌驾200,建议使用Text类型。接纳 UTF-8 编码的字节次序Booleantrue/false-false < trueByte                                                  (                                  −                                               2                                     7                                              )                                  ∼                                  (                                               2                                     7                                              −                                  1                                  )                                          (-2^7)\sim(2^7-1)                           (−27)∼(27−1)-数字次序Short                                                  (                                  −                                               2                                     15                                              )                                  ∼                                  (                                               2                                     15                                              −                                  1                                  )                                          (-2^{15})\sim(2^{15}-1)                           (−215)∼(215−1)-Integer                                                  (                                  −                                               2                                     31                                              )                                  ∼                                  (                                               2                                     31                                              −                                  1                                  )                                          (-2^{31})\sim(2^{31}-1)                           (−231)∼(231−1)-Long                                                  (                                  −                                               2                                     63                                              )                                  ∼                                  (                                               2                                     63                                              −                                  1                                  )                                          (-2^{63})\sim(2^{63}-1)                           (−263)∼(263−1)由于JavaScript不支持数据类型“Long”,Web SDK通过引入第三方开源组件实现支持数据类型“Long”的能力。“Long”类型的使用方法请参考https://github.com/dcodeIO/long.js。Float-3.40E+38 ~ +3.40E+38-Double-1.79E+308 ~ +1.79E+308-ByteArray-一般用于文件类型的数据存储,如图片、文档和视频等。在端侧时,使用Android开发应用时,以byte[]表示为字节数组。接纳 UTF-8 编码的字节次序Text--Date--时间次序IntAutoIncrement                                                  1                                  ∼                                  (                                               2                                     31                                              −                                  1                                  )                                          1\sim(2^{31}-1)                           1∼(231−1)Android、HarmonyOS(Java)和iOS不支持此数据类型。数字次序LongAutoIncrement                                                  1                                  ∼                                  (                                               2                                     63                                              −                                  1                                  )                                          1\sim(2^{63}-1)                           1∼(263−1)Android、HarmonyOS(Java)和iOS不支持此数据类型。数字次序
  1. student 表
  2. id         name   age
  3. 1    李四    20
  4. 2    王五    16
  5. 3         赵六    16
复制代码
权限管理

【角色】共有四种

【权限】共有三种:Read 查询、Upsert 新增和修改、Delete 删除
例如
  1. article 表
  2. id        title                                                                 
  3.                                                                                         用户 111
  4. 1   探索自我成长的道路:我的心路历程
  5. 2   如何在忙碌的生活中保持身心健康
  6. 3   分享我的旅行经历:探索世界的美
  7.                                                                                         用户 222
  8. 4        金融投资的新趋势:数字货币的崛起
  9. 5   未来科技发展展望:人工智能对社会的影响
复制代码
从上面我们可知,华为的云数据库,权限设置比较粗糙,还是必要配合业务代码,进行
它的权限设置如下:
  1. "permissions": [
  2.   {
  3.     "rights": ["Read"],
  4.     "role": "World"
  5.   },
  6.   {
  7.     "rights": ["Read","Upsert"],
  8.     "role": "Authenticated"
  9.   },
  10.   {
  11.     "rights": ["Read","Upsert","Delete"],
  12.     "role": "Creator"
  13.   },
  14.   {
  15.     "rights": ["Read","Upsert","Delete"],
  16.     "role": "Administrator"
  17.   }
  18. ]
复制代码
则有:

准备云数据库

存储区

新建对象的类型,其实就是新建表





另一种方法准备云数据库

使用云端一体化模板
新建一张表











端侧调用云数据库

调用云数据库又分成了两种方式
一种叫端测调用:使用ArkTs它的API,然后直接去使用云数据库
另外一种叫云测调用: 云函数间接的使用云数据库
起首明确:云数据库必要对接华为帐号系统(与前面提到的权限相关联)
分成两种方式:
一. 直接在端侧调用数据库,优点是更直接面对数据库,而且根据文档具备缓存模式的利益
二. 通过云函数在云侧调用数据库,可能无法利用缓存模式的一些利益(未验证)。但又可以隐藏数据库细节,并具备云函数的优点
端侧调用

0)前提

这三步与调用云函数相同

long类型。js不支持,必要用到第三方库
每次我们做了新的设置,都要重新下载 AGC 设置文件

初始化 AGC 连接器

1)建立模子



js 代表端侧模子代码
serverSDK代表云侧模子代码



导出结果如下
  1. /*
  2. * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
  3. * Generated by the CloudDB ObjectType compiler. DO NOT EDIT!
  4. */
  5. class t_user {
  6.     constructor() {
  7.         this.id = undefined;
  8.         this.name = "";
  9.         this.birthday = undefined;
  10.     }
  11.     setId(id) {
  12.         this.id = id;
  13.     }
  14.     getId() {
  15.         return this.id;
  16.     }
  17.     setName(name) {
  18.         this.name = name;
  19.     }
  20.     getName() {
  21.         return this.name;
  22.     }
  23.     setBirthday(birthday) {
  24.         this.birthday = birthday;
  25.     }
  26.     getBirthday() {
  27.         return this.birthday;
  28.     }
  29. }
  30. t_user.className = 't_user';
  31. export {t_user}
复制代码
2)导出 schema



导出数据库 schema 文件,例如下面是一个文件名 com.example.myapplication_21_cn.json,格式为【应用名_版本_语言.json】
3)初始化 database











改进–分页查询






改进–搜索功能

改进下滑显示搜索框
只有下滑到顶部的时间,才显示搜索框


  1. // @ts-ignore
  2. import schema from './com.example.myapplication_21_cn.json'
  3. @Entry
  4. @Component
  5. struct XXX {
  6.   // ...  
  7.   private database: Database
  8.   async aboutToAppear() {
  9.     try {
  10.       // 根据 schema 和 zoneName 创建数据库对象
  11.       this.database = cloud.database({ objectTypeInfo: schema, zoneName: "shopping" })
  12.     } catch (e) {
  13.       hilog.error(0, 'User Query', 'error:' + JSON.stringify(e))
  14.     }
  15.   }
复制代码
4)查询

  1. import cloud, { Database } from '@hw-agconnect/cloud'
  2. // @ts-ignore
  3. import schema from '../../resources/rawfile/schema.json'
  4. import { t_student } from '../model/t_student'
  5. import hilog from '@ohos.hilog'
  6. @Entry
  7. @Component
  8. struct StudentPage {
  9.   @State studentList: t_student[] = []
  10.   private database: Database = null
  11.   private dataOffset: number = 0
  12.   @State searchAge: string = ''
  13.   @State showSearch: boolean = false
  14.   private startY: number = 0
  15.   private startIndex: number = 0
  16.   aboutToAppear() {
  17.     this.database = cloud.database({
  18.       zoneName: 'test',
  19.       objectTypeInfo: schema
  20.     })
  21.     this.search('', '', '', this.dataOffset)
  22.   }
  23.   async search(id: string, name: string, age: string, offset: number, limit: number = 10) {
  24.     try {
  25.       const query = this.database.collection(t_student).query()
  26.       if (id.length > 0) {
  27.         // where id=?
  28.         query.equalTo("id", Number(id))
  29.       }
  30.       if (name.length > 0) {
  31.         // where name like ?
  32.         query.contains("name", name)
  33.       }
  34.       if (age.length > 0) {
  35.         // where age=?
  36.         query.equalTo("age", Number(age))
  37.       }
  38.       query.limit(limit, offset)
  39.       query.orderByAsc("id")
  40.       const list: t_student[] = await query.get()
  41.       this.studentList.push(...list)
  42.       hilog.info(0, 'Query', `${list.map(s => s.id)}`)
  43.     } catch (e) {
  44.       hilog.error(0, 'Query', JSON.stringify(e))
  45.     }
  46.   }
  47.   build() {
  48.     Column({ space: 26 }) {
  49.       Row() {
  50.         Text(`编号`)
  51.           .fontSize(18)
  52.           .fontWeight(FontWeight.Bold)
  53.           .fontColor('white')
  54.           .textAlign(TextAlign.Center)
  55.           .width('33%')
  56.         Text(`姓名`)
  57.           .fontSize(18)
  58.           .fontWeight(FontWeight.Bold)
  59.           .fontColor('white')
  60.           .textAlign(TextAlign.Center)
  61.           .width('34%')
  62.         Text(`年龄`)
  63.           .fontSize(18)
  64.           .fontWeight(FontWeight.Bold)
  65.           .fontColor('white')
  66.           .textAlign(TextAlign.Center)
  67.           .width('33%')
  68.       }
  69.       .width('100%')
  70.       .height(36)
  71.       .backgroundColor('black')
  72.       if (this.showSearch) {
  73.         Search()
  74.           .searchButton('搜索')
  75.           .onChange(value => {
  76.             this.searchAge = value
  77.           })
  78.           .onSubmit(() => {
  79.             this.studentList = []
  80.             this.dataOffset = 0
  81.             this.search('', '', this.searchAge, this.dataOffset)
  82.             this.showSearch = false
  83.           })
  84.       }
  85.       List({ space: 52 }) {
  86.         ForEach(this.studentList, (stu: t_student) => {
  87.           ListItem() {
  88.             Row() {
  89.               Text(`${stu.getId()}`)
  90.                 .fontSize(18)
  91.                 .textAlign(TextAlign.Center)
  92.                 .width('33%')
  93.               Text(stu.getName())
  94.                 .fontSize(18)
  95.                 .textAlign(TextAlign.Center)
  96.                 .width('34%')
  97.               Text(`${stu.getAge()}`)
  98.                 .fontSize(18)
  99.                 .textAlign(TextAlign.Center)
  100.                 .width('33%')
  101.             }
  102.             .width('100%')
  103.           }
  104.         })
  105.       }
  106.       .width('100%')
  107.       .height('82%')
  108.       .onReachEnd(() => {
  109.         this.dataOffset += 10
  110.         this.search('', '', '', this.dataOffset)
  111.       })
  112.       .onScrollIndex((start, end) => {
  113.         hilog.info(0, 'Scroll', `start: ${start} end:${end}`)
  114.         this.startIndex = start
  115.       })
  116.       .onTouch(event => {
  117.         switch (event.type) {
  118.           case TouchType.Down:
  119.             this.startY = event.touches[0].y
  120.             break;
  121.           case TouchType.Move:
  122.             const endY = event.touches[0].y
  123.             if (endY - this.startY >= 35 && this.startIndex === 0) {
  124.               this.showSearch = true
  125.             } else if (this.startY - endY >= 25) {
  126.               this.showSearch = false
  127.             }
  128.             break;
  129.         }
  130.       })
  131.       Divider()
  132.     }
  133.     .width('100%')
  134.     .justifyContent(FlexAlign.Start)
  135.   }
  136. }
复制代码







实现左滑删除

有个难点bind
函数作为参数通报,this丢失问题
函数作为参数通报,剩余参数问问题
问题演示


问题出现



5)新增、修改、删除

  1. import cloud, { Database } from '@hw-agconnect/cloud'
  2. // @ts-ignore
  3. import schema from '../../resources/rawfile/schema.json'
  4. import { t_student } from '../model/t_student'
  5. import hilog from '@ohos.hilog'
  6. import { StudentInsertDialog } from './StudentInsertDialog'
  7. import router from '@ohos.router'
  8. import { StudentUpdateDialog } from './StudentUpdateDialog'
  9. @Entry
  10. @Component
  11. struct StudentPage {
  12.   @State studentList: t_student[] = []
  13.   private database: Database = null
  14.   private dataOffset: number = 0
  15.   @State searchAge: string = ''
  16.   @State showSearch: boolean = false
  17.   private startY: number = 0
  18.   private startIndex: number = 0
  19.   private studentInsertDialogController: CustomDialogController = new CustomDialogController({
  20.     builder: StudentInsertDialog({
  21.       confirm: async (name, age) => {
  22.         try {
  23.           const stu = new t_student()
  24.           stu.setName(name)
  25.           stu.setAge(age)
  26.           await this.database.collection(t_student).upsert(stu)
  27.           AlertDialog.show({ message: '添加学生成功' })
  28.           hilog.info(0, 'Student Insert', 'Success')
  29.           this.dataOffset = 0
  30.           this.studentList = []
  31.           this.search('', '', '', this.dataOffset)
  32.         } catch (e) {
  33.           hilog.error(0, 'Student Insert', JSON.stringify(e))
  34.         }
  35.       }
  36.     })
  37.   })
  38.   private studentUpdateDialogController: CustomDialogController
  39.   openStudentUpdateDialog(stu: t_student) {
  40.     this.studentUpdateDialogController = new CustomDialogController({
  41.       builder: StudentUpdateDialog({
  42.         sid: stu.id, name: stu.name, age: stu.age,
  43.         confirm: async (id, name, age) => {
  44.           try {
  45.             const stu = new t_student()
  46.             stu.setId(id)
  47.             stu.setName(name)
  48.             stu.setAge(age)
  49.             await this.database.collection(t_student).upsert(stu)
  50.             AlertDialog.show({ message: '修改学生成功' })
  51.             hilog.info(0, 'Student Update', 'Success')
  52.             this.dataOffset = 0
  53.             this.studentList = []
  54.             this.search('', '', '', this.dataOffset)
  55.           } catch (e) {
  56.             hilog.error(0, 'Student Update', JSON.stringify(e))
  57.           }
  58.         }
  59.       })
  60.     })
  61.     this.studentUpdateDialogController.open()
  62.   }
  63.   openStudentDeleteDialog(stu: t_student) {
  64.     AlertDialog.show({
  65.       title: '请确认',
  66.       message: '真的要删除该用户吗?',
  67.       confirm: {
  68.         value: '确定',
  69.         action: async () => {
  70.           try {
  71.             await this.database.collection(t_student).delete(stu)
  72.             AlertDialog.show({ message: '删除学生成功' })
  73.             hilog.info(0, 'Student Delete', 'Success')
  74.             this.dataOffset = 0
  75.             this.studentList = []
  76.             this.search('', '', '', this.dataOffset)
  77.           } catch (e) {
  78.             hilog.error(0, 'Student Delete', JSON.stringify(e))
  79.           }
  80.         }
  81.       }
  82.     })
  83.   }
  84.   @Builder
  85.   showOperation(stu: t_student) {
  86.     Row({ space: 12 }) {
  87.       Image($r("app.media.ic_public_edit_filled"))
  88.         .width(20)
  89.         .onClick(() => {
  90.           this.openStudentUpdateDialog(stu)
  91.         })
  92.       Image($r("app.media.ic_public_delete_filled"))
  93.         .width(21)
  94.         .onClick(() => {
  95.           this.openStudentDeleteDialog(stu)
  96.         })
  97.     }
  98.     .width(80)
  99.     .justifyContent(FlexAlign.Start)
  100.   }
  101.   aboutToAppear() {
  102.     this.database = cloud.database({
  103.       zoneName: 'test',
  104.       objectTypeInfo: schema
  105.     })
  106.     this.search('', '', '', this.dataOffset)
  107.   }
  108.   async search(id: string, name: string, age: string, offset: number, limit: number = 10) {
  109.     try {
  110.       const query = this.database.collection(t_student).query()
  111.       if (id.length > 0) {
  112.         // where id=?
  113.         query.equalTo("id", Number(id))
  114.       }
  115.       if (name.length > 0) {
  116.         // where name like ?
  117.         query.contains("name", name)
  118.       }
  119.       if (age.length > 0) {
  120.         // where age=?
  121.         query.equalTo("age", Number(age))
  122.       }
  123.       query.limit(limit, offset)
  124.       query.orderByAsc("id")
  125.       const list: t_student[] = await query.get()
  126.       this.studentList.push(...list)
  127.       hilog.info(0, 'Student Query', `${list.map(s => s.id)}`)
  128.     } catch (e) {
  129.       hilog.error(0, 'Student Query', JSON.stringify(e))
  130.     }
  131.   }
  132.   build() {
  133.     Column() {
  134.       Row() {
  135.         Text(`编号`)
  136.           .fontSize(18)
  137.           .fontWeight(FontWeight.Bold)
  138.           .fontColor('white')
  139.           .textAlign(TextAlign.Center)
  140.           .width('33%')
  141.         Text(`姓名`)
  142.           .fontSize(18)
  143.           .fontWeight(FontWeight.Bold)
  144.           .fontColor('white')
  145.           .textAlign(TextAlign.Center)
  146.           .width('34%')
  147.         Text(`年龄`)
  148.           .fontSize(18)
  149.           .fontWeight(FontWeight.Bold)
  150.           .fontColor('white')
  151.           .textAlign(TextAlign.Center)
  152.           .width('33%')
  153.       }
  154.       .width('100%')
  155.       .height(36)
  156.       .backgroundColor('black')
  157.       if (this.showSearch) {
  158.         Search()
  159.           .searchButton('搜索')
  160.           .onChange(value => {
  161.             this.searchAge = value
  162.           })
  163.           .onSubmit(() => {
  164.             this.studentList = []
  165.             this.dataOffset = 0
  166.             this.search('', '', this.searchAge, this.dataOffset)
  167.             this.showSearch = false
  168.           })
  169.       }
  170.       List({ space: 48 }) {
  171.         ForEach(this.studentList, (stu: t_student) => {
  172.           ListItem() {
  173.             Row() {
  174.               Text(`${stu.getId()}`)
  175.                 .fontSize(18)
  176.                 .textAlign(TextAlign.Center)
  177.                 .width('33%')
  178.               Text(stu.getName())
  179.                 .fontSize(18)
  180.                 .textAlign(TextAlign.Center)
  181.                 .width('34%')
  182.               Text(`${stu.getAge()}`)
  183.                 .fontSize(18)
  184.                 .textAlign(TextAlign.Center)
  185.                 .width('33%')
  186.             }
  187.             .width('100%')
  188.           }
  189.           .swipeAction({ end: this.showOperation.bind(this, stu) })
  190.         })
  191.       }
  192.       .width('100%')
  193.       .height('78%')
  194.       .margin({ top: 24, bottom: 24 })
  195.       .onReachEnd(() => {
  196.         this.dataOffset += 10
  197.         this.search('', '', '', this.dataOffset)
  198.       })
  199.       .onScrollIndex((start, end) => {
  200.         hilog.info(0, 'Scroll', `start: ${start} end:${end}`)
  201.         this.startIndex = start
  202.       })
  203.       .onTouch(event => {
  204.         switch (event.type) {
  205.           case TouchType.Down:
  206.             this.startY = event.touches[0].y
  207.             break;
  208.           case TouchType.Move:
  209.             const endY = event.touches[0].y
  210.             if (endY - this.startY >= 25 && this.startIndex === 0) {
  211.               this.showSearch = true
  212.             } else if (this.startY - endY >= 5 || this.startIndex === 0) {
  213.               this.showSearch = false
  214.             }
  215.             break;
  216.         }
  217.       })
  218.       Button('新增', { type: ButtonType.Normal })
  219.         .width('100%')
  220.         .margin({ bottom: 6 })
  221.         .onClick(async () => {
  222.           this.studentInsertDialogController.open()
  223.         })
  224.       Button('登出', { type: ButtonType.Normal })
  225.         .width('100%')
  226.         .backgroundColor(0xcccccc)
  227.         .margin({ bottom: 6 })
  228.         .onClick(async () => {
  229.           try {
  230.             await cloud.auth().signOut()
  231.             hilog.info(0, 'SignOut', 'Success')
  232.             router.replaceUrl({ url: 'pages/MyLoginCustom' })
  233.           } catch (e) {
  234.             hilog.error(0, 'SignOut', JSON.stringify(e))
  235.           }
  236.         })
  237.       Divider()
  238.     }
  239.     .width('100%')
  240.     .justifyContent(FlexAlign.Start)
  241.   }
  242. }
复制代码
  1. @CustomDialog
  2. export struct StudentInsertDialog {
  3.   private name: string = ''
  4.   private age: number = 18
  5.   controller: CustomDialogController
  6.   confirm: (name: string, age: number) => void
  7.   private static range = (start, stop, step = 1) => Array.from({
  8.     length: (stop - start - 1) / step + 1
  9.   }, (_, i) => start + (i * step))
  10.   static AgeRange: string[] = StudentInsertDialog.range(16, 60).map(i => String(i))
  11.   build() {
  12.     Column({ space: 15 }) {
  13.       Row() {
  14.         Text('姓名')
  15.           .width('20%')
  16.         TextInput({ text: this.name })
  17.           .width('80%')
  18.           .onChange(value => {
  19.             this.name = value
  20.           })
  21.       }
  22.       .width('80%')
  23.       .justifyContent(FlexAlign.SpaceAround)
  24.       Row() {
  25.         Text('年龄')
  26.           .width('20%')
  27.         TextPicker({ range: StudentInsertDialog.AgeRange, selected: 2 })
  28.           .width('80%')
  29.           .onChange(value => {
  30.             this.age = Number(value)
  31.           })
  32.       }
  33.       .width('80%')
  34.       Row() {
  35.         Button('取消')
  36.           .onClick(() => {
  37.             this.controller.close()
  38.           })
  39.           .backgroundColor(0xcccccc)
  40.         Button('确定')
  41.           .onClick(() => {
  42.             this.confirm(this.name, this.age)
  43.             this.controller.close()
  44.           })
  45.       }
  46.       .width('80%')
  47.       .justifyContent(FlexAlign.SpaceAround)
  48.     }
  49.     .justifyContent(FlexAlign.SpaceAround)
  50.     .width('80%')
  51.     .height('55%')
  52.     .margin({ top: 15 })
  53.   }
  54. }
复制代码
  1. import { StudentInsertDialog } from './StudentInsertDialog'
  2. @CustomDialog
  3. export struct StudentUpdateDialog {
  4.   private sid: number
  5.   private name: string
  6.   private age: number
  7.   controller: CustomDialogController
  8.   confirm: (id: number, name: string, age: number) => void
  9.   build() {
  10.     Column({ space: 15 }) {
  11.       Text(`${this.sid}`)
  12.       Row() {
  13.         Text('姓名')
  14.           .width('20%')
  15.         TextInput({ text: this.name })
  16.           .width('80%')
  17.           .onChange(value => {
  18.             this.name = value
  19.           })
  20.       }
  21.       .width('80%')
  22.       .justifyContent(FlexAlign.SpaceAround)
  23.       Row() {
  24.         Text('年龄')
  25.           .width('20%')
  26.         TextPicker({ range: StudentInsertDialog.AgeRange, selected: 2 })
  27.           .width('80%')
  28.           .onChange(value => {
  29.             this.age = Number(value)
  30.           })
  31.       }
  32.       .width('80%')
  33.       Row() {
  34.         Button('取消')
  35.           .onClick(() => {
  36.             this.controller.close()
  37.           })
  38.           .backgroundColor(0xcccccc)
  39.         Button('确定')
  40.           .onClick(() => {
  41.             this.confirm(this.sid, this.name, this.age)
  42.             this.controller.close()
  43.           })
  44.       }
  45.       .width('80%')
  46.       .justifyContent(FlexAlign.SpaceAround)
  47.     }
  48.     .justifyContent(FlexAlign.SpaceAround)
  49.     .width('80%')
  50.     .height('65%')
  51.     .margin({ top: 15 })
  52.   }
  53. }
复制代码
云侧调用

这部分代码可以自动天生,不作为重点解说

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4