HarmonyOS鸿蒙应用开发(三、轻量级配置存储dataPreferences)

这篇具有很好参考价值的文章主要介绍了HarmonyOS鸿蒙应用开发(三、轻量级配置存储dataPreferences)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在应用开发中存储一些配置是很常见的需求。在android中有SharedPreferences,一个轻量级的存储类,用来保存应用的一些常用配置。在HarmonyOS鸿蒙应用开发中,实现类似功能的也叫首选项,dataPreferences。

相关概念

ohos.data.preferences (用户首选项)

dataPreferences(首选项),为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。数据存储形式为键值对,键的类型为字符串型,值的存储数据类型包括数字型、字符型、布尔型以及这3种类型的数组类型。

如何使用

导入模块

import dataPreferences from '@ohos.data.preferences';

常量

系统能力: SystemCapability.DistributedDataManager.Preferences.Core

名称

参数类型

可读

可写

说明

MAX_KEY_LENGTH

number

Key的最大长度限制为80个字节。

MAX_VALUE_LENGTH

number

Value的最大长度限制为8192个字节。

dataPreferences.getPreferences

getPreferences(context: Context, name: string, callback: AsyncCallback<Preferences>): void

获取Preferences实例,使用callback异步回调。

系统能力: SystemCapability.DistributedDataManager.Preferences.Core

Stage模型使用示例

import UIAbility from '@ohos.app.ability.UIAbility';

let preferences = null;

class EntryAbility extends UIAbility {
    onWindowStageCreate(windowStage) {
        try {
            dataPreferences.getPreferences(this.context, 'mystore', function (err, val) {
                if (err) {
                    console.error("Failed to get preferences. code =" + err.code + ", message =" + err.message);
                    return;
                }
                preferences = val;
                console.info("Succeeded in getting preferences.");
            })
        } catch (err) {
            console.error("Failed to get preferences. code =" + err.code + ", message =" + err.message);
        }
    }
}

首选项使用demo

软件要求

  • DevEco Studio版本:DevEco Studio 3.1 Release。
  • HarmonyOS SDK版本:API version 9。

HarmonyOS鸿蒙应用开发(三、轻量级配置存储dataPreferences),OpenHarmony入门教程,harmonyos,华为,OpenHarmony,android

代码结构

├──entry/src/main/ets            // 代码区
│  ├──common
│  │  ├──constants
│  │  │  ├──CommonConstants.ets    // 公共常量类
│  │  │  └──StyleConstants.ets    // 样式常量类 
│  │  └──utils
│  │     └──Logger.ets        // 日志打印类
│  ├──entryability
│  │  └──EntryAbility.ts    // 程序入口类
│  ├──model
│  │  └──PreferenceModel.ets    // 首选项相关方法类
│  ├──pages
│  │  └──Index.ets        // 主界面    
│  ├──view
│  │  ├──ButtonComponent.ets    // 自定义Button组件类
│  │  └──TextItemComponent.ets  // 自定义Text组件类
│  └──viewmodel
│     ├──ButtonItemData.ets     // 按钮数据类
│     └──Fruit.ets            // 水果数据类
└──entry/src/main/resources    // 资源文件目录

构建主界面

在这个任务中,我们将完成示例主界面的开发,效果如图所示:

HarmonyOS鸿蒙应用开发(三、轻量级配置存储dataPreferences),OpenHarmony入门教程,harmonyos,华为,OpenHarmony,android

从上面效果图可以看出,主界面主要由2个相同样式的文本和文本输入框,以及3个相同样式的按钮组成。我们可以将文本和文本输入框抽取成一个TextItemComponent子组件。再将按钮抽取成一个ButtonComponent子组件。

在ets目录下,点击鼠标右键 > New > Directory,新建名为view的自定义子组件目录。然后在view目录下,点击鼠标右键 > New > ArkTS File,新建两个ArkTS文件,分别为TextItemComponent子组件、ButtonComponent子组件。可以看到文件目录结构,效果如图所示: 

HarmonyOS鸿蒙应用开发(三、轻量级配置存储dataPreferences),OpenHarmony入门教程,harmonyos,华为,OpenHarmony,android

// TextItemComponent.ets
@Component
export default struct TextItemComponent {
  private textResource: Resource = $r('app.string.empty'); // 按钮文本资源
  private placeholderResource: Resource = $r('app.string.empty'); // placeholder文本资源
  private marginBottom: string = '';
  private marginTop: string = '';
  private textInputType: InputType = InputType.Normal; // 输入框输入数据类型
  private textFlag: number = 0; // 文本框标记
  @Link fruit: Fruit; // 水果数据
  private textInputCallBack = (value: string) => {}; // TextInput的回调

  build() {
    Column() {
      Text(this.textResource)
        .fontSize(StyleConstants.TEXT_FONT_SIZE)
        .height(StyleConstants.TEXT_HEIGHT)
        .width(StyleConstants.FULL_PERCENT)
        .fontColor($r('app.color.text_font_color'))
        .letterSpacing(StyleConstants.LETTER_SPACING)
        .fontWeight(StyleConstants.FONT_WEIGHT)
        .margin({
          bottom: StyleConstants.TEXT_MARGIN_BOTTOM,
          left: StyleConstants.TEXT_MARGIN_LEFT,
          top: this.marginTop
        })

      TextInput({
        placeholder: this.placeholderResource,
        text: this.textFlag === 0 ? (this.fruit.fruitName) : (this.fruit.fruitNum)
      })
        .placeholderFont({ size: StyleConstants.FONT_SIZE, weight: StyleConstants.FONT_WEIGHT })
        .placeholderColor($r('app.color.placeholder_color'))
        .caretColor(Color.Blue)
        .type(this.textInputType)
        .height(StyleConstants.TEXT_INPUT_HEIGHT)
        .width(StyleConstants.TEXT_INPUT_WIDTH)
        .margin({ bottom: this.marginBottom })
        .fontSize(StyleConstants.FONT_SIZE)
        .fontColor($r('app.color.text_font_color'))
        .fontWeight(StyleConstants.FONT_WEIGHT)
        .backgroundColor($r('app.color.white'))
        .onChange((value: string) => {
          this.textInputCallBack(value);
        })
    }
  }
}
// ButtonComponent.ets
@Component
export default struct ButtonComponent {
  private buttonItemValues: Array<ButtonItemData> = this.getButtonItemValues();
  @Link fruit: Fruit; // 水果数据 

  build() {
    Column() {
      ForEach(this.buttonItemValues, (item: ButtonItemData) => {
        Button(item.resource, { type: ButtonType.Capsule, stateEffect: true })
          .backgroundColor($r('app.color.button_background_color'))
          .width(StyleConstants.BUTTON_WIDTH)
          .height(StyleConstants.BUTTON_HEIGHT)
          .fontWeight(StyleConstants.FONT_WEIGHT)
          .fontSize(StyleConstants.FONT_SIZE)
          .margin({ bottom: StyleConstants.BUTTON_MARGIN_BOTTOM })
          .onClick(() => {
            item.clickMethod();
          })
      }, (item: ButtonItemData) => JSON.stringify(item))
    }
  }
}

在Index.ets主界面中引用TextItemComponent和ButtonComponent子组件。 

// Index.ets
Column() {
  // 水果名称输入框
  TextItemComponent({
    textResource: $r('app.string.fruit_text'),
    placeholderResource: $r('app.string.fruit_placeholder'),
    textFlag: CommonConstants.FRUIT_FLAG,
    fruit: $fruit,
    textInputCallBack: (value: string) => {
      this.fruit.fruitName = value;
    }
  })

  // 水果数量输入框
  TextItemComponent({
    textResource: $r('app.string.number_text'),
    placeholderResource: $r('app.string.number_placeholder'),
    textFlag: CommonConstants.NUMBER_FLAG,
    fruit: $fruit,
    textInputCallBack: (value: string) => {
      this.fruit.fruitNum = value;
    }
  })

  // 按钮组件
  ButtonComponent({ fruit: $fruit })
}
.width(StyleConstants.FULL_PERCENT)
.height(StyleConstants.FULL_PERCENT)
.backgroundColor($r('app.color.main_background_color'))

创建数据文件

创建数据文件需要如下两个步骤:

  • 导入dataPreferences模块。

  • 通过dataPreferences模块的getPreferences(context, name)方法获取到对应文件名的Preferences实例。

Preferences的数据存储在文件中,因此需要指定存储的文件名PREFERENCES_NAME。再通过Preferences提供的方法进行数据库的相关操作。

// PreferenceModel.ets
// 导入dataPreferences模块
import dataPreferences from '@ohos.data.preferences';

let context = getContext(this);
let preference: dataPreferences.Preferences;
let preferenceTemp: dataPreferences.Preferences;

// 调用getPreferences方法读取指定首选项持久化文件,将数据加载到Preferences实例,用于数据操作
async getPreferencesFromStorage() {
  try {
    preference = await dataPreferences.getPreferences(context, CommonConstants.PREFERENCES_NAME);
  } catch (err) {
    Logger.error(CommonConstants.TAG, `Failed to get preferences, Cause: ${err}`);
  }
}

写入数据

获取Preferences实例后,使用Preferences的put方法,将用户输入的水果名称和水果数量数据,保存到缓存的实例中。再通过Preferences的flush方法将Preferences实例异步存储到首选项持久化文件中。

// PreferenceModel.ets
async putPreference(fruit: Fruit) {
  ...
  try {
    // 将用户输入的水果名称和水果数量数据,保存到缓存的Preference实例中
    await preference.put(CommonConstants.KEY_NAME, JSON.stringify(fruit));
  } catch (err) {
    Logger.error(CommonConstants.TAG, `Failed to put value, Cause: ${err}`);
  }
  // 将Preference实例存储到首选项持久化文件中
  await preference.flush();
}

读取数据

使用Preferences的get方法读取数据。如果键不存在,则返回默认值。例如获取下面代码中fruit的值,如果fruit的键KEY_NAME不存在,则会返回空字符串。通过默认值的设置,来避免程序出现异常。

// PreferenceModel.ets
async getPreference() {
  let fruit = '';
  ...
  try {
    fruit = (await preference.get(CommonConstants.KEY_NAME, '')).toString();
  } catch (err) {
    Logger.error(CommonConstants.TAG, `Failed to get value, Cause: ${err}`);
  }
}

删除数据文件

通过dataPreferences模块的deletePreferences(context, name)方法从内存中移除指定文件对应的Preferences单实例。移除Preferences单实例时,应用不允许再使用该实例进行数据操作,否则会出现数据一致性问题。

// PreferenceModel.ets
async deletePreferences() {
  try {
    await dataPreferences.deletePreferences(context, CommonConstants.PREFERENCES_NAME);
  } catch(err) {
    Logger.error(CommonConstants.TAG, `Failed to delete preferences, Cause: ${err}`);
  }
  ...
}

附上demo源码地址:demo源码

模块化封装

import dataPreferences from '@ohos.data.preferences'
/**
 * 项目全局存储,使用ohos.data.preferences
 */
const defaultPreferenceName = "YOUR_GLOBAL_PREFERENCES_CONF"

type ValueType = number | string | boolean | Array<number> | Array<string> | Array<boolean>;


export class StorageUtils {
  private constructor() {
  }

  static readonly context = getContext();

  /**
   * 获取Prefer实例
   * @param preferenceName
   * @returns
   */
  static async getPreferences(preferenceName: string = defaultPreferenceName) {
    return await dataPreferences.getPreferences(this.context, preferenceName)
  }

  static async put(key: string, value: ValueType, preferenceName: string = defaultPreferenceName) {
    // 获取实例
    let preferences = await this.getPreferences(preferenceName)
    await preferences.put(key, value)
    //Tips Point ---此处一定要flush,要不然不能永久序列化到本地
    await preferences.flush()
  }
  /**
   * 获取值
   * @param key
   * @param defaultValue
   * @param preferenceName
   * @returns
   */
  static async get(key: string, defaultValue: ValueType = "", preferenceName: string = defaultPreferenceName) {
    // 获取实例
    let preferences = await this.getPreferences(preferenceName)
    return preferences.get(key, defaultValue)
  }

  /**
   * 删除值
   * @param key
   * @param preferenceName
   * @returns
   */
  static async delete(key: string, preferenceName: string = defaultPreferenceName) {
    let preferences = await this.getPreferences(preferenceName)
    return await preferences.delete(key)
  }

  /**
   * 清空
   * @param preferenceName
   * @returns
   */
  static async clear(preferenceName: string = defaultPreferenceName) {
    let preferences = await this.getPreferences(preferenceName)
    return await preferences.clear()
  }
}

封装后的使用

封装后的使用变得很简单了,举例如下:

//......
StorageUtils.put(StorageKeys.USER_LOGIN, true)
StorageUtils.put(StorageKeys.USER_INFO_KEY, "allen")

//
StorageUtils.get(StorageKeys.USER_LOGIN).then((result) => {
            showToast(result.toString())
          }

setTimeout(() => {
          StorageUtils.get(StorageKeys.USER_LOGIN).then((result) => {
            showToast(result.toString())
          })
        }, 3000)

let hasLogin = await StorageUtils.get(StorageKeys.USER_LOGIN, false)
        LogUtils.debug('网络请求Request 登录状态校验>>>', `${hasLogin.toString()}`);

注意和AppStorage区别

AppStorage是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。

和AppStorage不同的是,LocalStorage是页面级的,通常应用于页面内的数据共享。而AppStorage是应用级的全局状态共享,还相当于整个应用的“中枢”,持久化数据PersistentStorage和环境变量Environment都是通过和AppStorage中转,才可以和UI交互。

AppStorage

AppStorage是在应用启动的时候会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorage将在应用运行过程保留其属性。属性通过唯一的键字符串值访问。

AppStorage可以和UI组件同步,且可以在应用业务逻辑中被访问。

AppStorage中的属性可以被双向同步,数据可以是存在于本地或远程设备上,并具有不同的功能,比如数据持久化(详见PersistentStorage)。这些数据是通过业务逻辑中实现,与UI解耦,如果希望这些数据在UI中使用,需要用到@StorageProp和@StorageLink。

由于AppStorage是内存内数据,该行为会导致数据丧失持久化能力。

@StorageProp

在上文中已经提到,如果要建立AppStorage和自定义组件的联系,需要使用@StorageProp和@StorageLink装饰器。使用@StorageProp(key)/@StorageLink(key)装饰组件内的变量,key标识了AppStorage的属性。

当自定义组件初始化的时候,@StorageProp(key)/@StorageLink(key)装饰的变量会通过给定的key,绑定在AppStorage对应的属性,完成初始化。本地初始化是必要的,因为无法保证AppStorage一定存在给定的key,这取决于应用逻辑,是否在组件初始化之前在AppStorage实例中存入对应的属性。

@StorageProp(key)是和AppStorage中key对应的属性建立单向数据同步,我们允许本地改变的发生,但是对于@StorageProp,本地的修改永远不会同步回AppStorage中,相反,如果AppStorage给定key的属性发生改变,改变会被同步给@StorageProp,并覆盖掉本地的修改。

PersistentStorage

最后需要注意的是,LocalStorage和AppStorage都是运行时的内存,但是在应用退出再次启动后,依然能保存选定的结果,需要用到PersistentStorage。

PersistentStorage是应用程序中的可选单例对象。此对象的作用是持久化存储选定的AppStorage属性,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。虽然借助PersistentStorage也可以实现数据的持久话,但它是用来持久化UI状态的,注意和用户首选项的区别(如果是一些配置存储,使用用户首选项)。

综上所述,什么时候用PersistentStorage?在应用退出再次启动后,依然能保存选定的结果,是应用开发中十分常见的现象,这就需要用到PersistentStorage。

限制条件

PersistentStorage允许的类型和值有:

  • number, string, boolean, enum 等简单类型。
  • 可以被JSON.stringify()和JSON.parse()重构的对象。例如Date, Map, Set等内置类型则不支持,以及对象的属性方法不支持持久化。

PersistentStorage不允许的类型和值有:

  • 不支持嵌套对象(对象数组,对象的属性是对象等)。因为目前框架无法检测AppStorage中嵌套对象(包括数组)值的变化,所以无法写回到PersistentStorage中。
  • 不支持undefined 和 null 。

持久化数据是一个相对缓慢的操作,应用程序应避免以下情况:

  • 持久化大型数据集。
  • 持久化经常变化的变量。

PersistentStorage的持久化变量最好是小于2kb的数据,不要大量的数据持久化,因为PersistentStorage写入磁盘的操作是同步的,大量的数据本地化读写会同步在UI线程中执行,影响UI渲染性能。如果开发者需要存储大量的数据,建议使用数据库api。

PersistentStorage只能在UI页面内使用,否则将无法持久化数据。

PersistentStorage将选定的AppStorage属性保留在设备磁盘上。应用程序通过API,以决定哪些AppStorage属性应借助PersistentStorage持久化。UI和业务逻辑不直接访问PersistentStorage中的属性,所有属性访问都是对AppStorage的访问,AppStorage中的更改会自动同步到PersistentStorage。

PersistentStorage和AppStorage中的属性建立双向同步。应用开发通常通过AppStorage访问PersistentStorage,另外还有一些接口可以用于管理持久化属性,但是业务逻辑始终是通过AppStorage获取和设置属性的。

更多介绍,参见官方文档:PersistentStorage:持久化存储UI状态

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注博主,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新VIP学习资料,请关注猫哥公众号【猫青年】,回复“鸿蒙”获取

HarmonyOS鸿蒙应用开发(三、轻量级配置存储dataPreferences),OpenHarmony入门教程,harmonyos,华为,OpenHarmony,android

其他资源

@ohos.data.preferences (用户首选项)

文档中心--Codelabs

AppStorage:应用全局的UI状态存储

健康生活应用(ArkTS)_华为开发者HarmonyOS专区小助手-华为开发者联盟HarmonyOS专区

harmonyOS鸿蒙-数据管理-用户首选项(@ohos.data.preferences)_harmonyos datapreferences-CSDN博客

HarmonyOS-AppStorage:应用全局的UI状态存储-CSDN博客  

HarmonyOS 网络请求以及数据持久化-CSDN博客

https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_HarmonyOS-HealthyLife

https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Preferences

HarmonyOS应用开发-首选项与后台通知管理_@ohos.data.preferences-CSDN博客文章来源地址https://www.toymoban.com/news/detail-815472.html

到了这里,关于HarmonyOS鸿蒙应用开发(三、轻量级配置存储dataPreferences)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【HarmonyOS】轻量级智能穿戴应用如何在页面中实现数据传递与全局变量的使用

    【】 轻量级智能穿戴、LiteWearable、数据传递、全局变量 【问题描述】 开发轻量级智能穿戴LiteWearable应用,在app.js中定义全局变量,在页面中通过this.$app.$def.xxx获取时,报错TypeError: Cannot read property \\\'$def\\\' of undefined 【问题分析】 经确认,LiteWearable暂不支持$def,只要是轻

    2024年02月03日
    浏览(33)
  • 【HarmonyOS】API6使用storage实现轻量级数据存储

     写在前面 本篇内容基于API6 JS语言进行开发,通过结合轻量级数据存储开发指导的文档,帮助大家完成一个实际的代码案例,通过这个小案例,可以实现简单数据的存储。 参考文档:文档中心 1、页面布局 首先我们编写一个简单的页面布局,页面中只有一个文本和两个按钮

    2024年02月14日
    浏览(27)
  • 鸿蒙(HarmonyOS)应用开发指南

    1.1 简介 鸿蒙 (即 HarmonyOS ,开发代号 Ark ,正式名称为华为终端鸿蒙智能设备操作系统软件)是华为公司自 2012 年以来开发的一款可支持鸿蒙原生应用和兼容 AOSP 应用的 分布式操作系统 。该系统利用“分布式”技术将 手机、电脑、平板、电视、汽车和智能穿戴 等多款设备

    2024年02月02日
    浏览(43)
  • HarmonyOS 鸿蒙应用开发(十一、面向鸿蒙开发的JavaScript基础)

    ArkTS 是HarmonyOS(鸿蒙操作系统)原生应用开发的首选语言。它是用于构建用户界面的一种TypeScript方言,扩展了TypeScript以适应HarmonyOS生态系统的UI开发需求。ArkTS 融合了TypeScript的静态类型系统和现代UI框架的设计理念,为开发者提供了一种更安全高效的方式来编写HarmonyOS应用。

    2024年02月20日
    浏览(39)
  • 鸿蒙应用开发尝鲜:初识HarmonyOS

    来源:华为官方网站 : https://developer.huawei.com/ 相信大家对鸿蒙应用开发也不在陌生,很多身处互联网行业或者不了解的人们现在也一定都听说过华为鸿蒙.这里我将不再说废话,直接步入正题 HarmonyOS应用开发采用的是ArkTS语言,ArkTS是在TypeScript(简称TS)基础上的拓展,而TS又是JavaScrip

    2024年02月02日
    浏览(42)
  • 鸿蒙HarmonyOS应用开发初体验

    最近华为发布mt60新机火了,作为一名移动开发程序员,对鸿蒙系统开发移动端就很感兴趣了。 开发工具:HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者 下载完后按默认安装就可以了,界面跟AS很类似,之前我jdk环境配置就不在配置了,不懂的可以百度下很多教程。 这是基

    2024年02月09日
    浏览(30)
  • 【HarmonyOS】开发一个可以看小姐姐的鸿蒙应用 鸿蒙开发入门

    先整张效果图,丑点是丑点,但可以用,买不起鸿蒙系统手机的我,只配用虚拟机。 要说目前最火的手机操作系统,要我来看的话那必然是鸿蒙无疑。16号刚刚结束了第五次鸿蒙内测,在看到这次的内测名单之后,居然有970的机器,这是不是说明俺这手里奋战了三年的荣耀

    2024年02月15日
    浏览(53)
  • 【HarmonyOS】鸿蒙应用开发基础认证题目

    【HarmonyOS】鸿蒙应用开发基础认证题目; 随着鸿蒙系统的不断发展,前不久,华为宣布了重磅消息,HarmonyOS next 开发者版本会在明年(2024)开放,并不再支持Android应用!这也意味着,移动端开发者今后又多了一个适配平台,也到了必须学的时候了。 目前已知一线大厂均已开

    2024年02月04日
    浏览(119)
  • 【鸿蒙开发】HarmonyOS应用开发者基础认证题库

    华为开发者学堂   1、考试需实名认证,请在考前于个人主页→个人信息→基本信息→进行实名认证,否则考试通过无法获取专业证书; 2、每个帐号每月有3次考试机会,次月重置考试次数。做题过程中请认真对待,避免考试次数浪费; 3、考试时长为1小时,请合理分配做题

    2024年03月09日
    浏览(72)
  • HarmonyOS鸿蒙原生应用开发设计- 图标库

    HarmonyOS设计文档中,为大家提供了独特的图标库,开发者可以根据需要直接引用。 图标库可以分为双色图标、填充图标、线性图标。具体分为 键盘、箭头、连接状态、媒体、人、设备、索引、通信、文件、物体与工具等。 整体分类 开发者直接使用官方提供的图标库内容,既

    2024年02月07日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包