鸿蒙ets开发App,开屏页,数据保存,隐私协议弹窗,广告页

初学鸿蒙的知识,在gitee上面看到别人写的demo,回看一下,再相应的修改,了解一下里面涉及的知识,顺便记录一下。里面的内容有转载也有自己的。感谢这么多的开发者无私的分享。

开屏页,每个app启动时的第一个页面,第一次安装时会显示让用户查看的隐私协议弹窗

在这里插入图片描述
在这里插入图片描述

LauncherPage启动页


import common from '@ohos.app.ability.common';
import preferences from '@ohos.data.preferences';
import router from '@ohos.router';
import Logger from '../common/utils/Logger';
import CommonConstants from '../common/constants/CommonConstants';
import CustomDialogComponent from '../view/CustomDialogComponent';

/**
 * The LauncherPage is the entry point of the application and shows how to develop the LauncherPage.
 * Stay on the LauncherPage for a few seconds to jump to the AdvertisingPage.
 * Developers can replace the background image.
 */
@Entry
@Component
struct LauncherPage {
  private context?: common.UIAbilityContext;//上下文
  private timerId: number = 0;//倒计时
  private isJumpToAdvertising: boolean = false;//是否跳转广告页
  //隐私协议弹窗
  dialogController: CustomDialogController = new CustomDialogController({
    builder: CustomDialogComponent(
      {
        cancel: () => {
          this.onCancel();
        },
        confirm: () => {
          this.onConfirm();
        }
      }),
    alignment: DialogAlignment.Center,
    offset: { dx: 0, dy: CommonConstants.DIALOG_CONTROLLER_DY_OFFSET },
    customStyle: true,
    autoCancel: false
  });
 //弹窗取消操作
  onCancel() {
    // Exit the application.
    this.context?.terminateSelf();
  }
//弹窗同意操作
  onConfirm() {
    // Save privacy agreement status.
    this.saveIsPrivacy();
  }
 //页面消失操作
  onPageHide() {
    if (this.isJumpToAdvertising) {
      router.clear();
    }
    // globalThis.isJumpPrivacy = true;
    GlobalContext.getContext().setObject('isJumpPrivacy', true);
    clearTimeout(this.timerId);
  }

  /**
   * Jump to advertising page.
   */
  jumpToAdvertisingPage() {
    this.timerId = setTimeout(() => {
      this.isJumpToAdvertising = true;
      router.pushUrl({
        url: CommonConstants.ADVERTISING_PAGE_URL
      }).catch((error: Error) => {
        Logger.error(CommonConstants.LAUNCHER_PAGE_TAG, 'LauncherPage pushUrl error ' + JSON.stringify(error));
      });
    }, CommonConstants.LAUNCHER_DELAY_TIME);
  }
/**
* 保存用户同意隐私协议的操作
*/
  saveIsPrivacy() {
    let preferences = this.getDataPreferences(this);
    preferences.then((res) => {
      res.put(CommonConstants.PREFERENCES_KEY_PRIVACY, true).then(() => {
        res.flush();
        Logger.info(CommonConstants.LAUNCHER_PAGE_TAG, 'Put the value of startup Successfully.');
      }).catch((err: Error) => {
        Logger.error(CommonConstants.LAUNCHER_PAGE_TAG, 'Get the preferences Failed, err: ' + err);
      });
    })
    this.jumpToAdvertisingPage();
  }
 //页面显示时时检查是否同意隐私协议
  onPageShow() {
    this.context = getContext(this) as common.UIAbilityContext;
    let preferences = this.getDataPreferences(this);
    preferences.then((res) => {
      res.get(CommonConstants.PREFERENCES_KEY_PRIVACY, false).then((isPrivate) => {
        Logger.info(CommonConstants.LAUNCHER_PAGE_TAG, 'onPageShow value: ' + isPrivate);
        if (isPrivate === true) {
           //跳转广告页
          this.jumpToAdvertisingPage();
        } else {
         //显示隐私协议弹窗
          this.dialogController.open();
        }
      });
    });
  }

  /**
   * Get data preferences action.
   */
  getDataPreferences(common: Object) {
    return preferences.getPreferences(getContext(common), CommonConstants.PREFERENCES_FILE_NAME);
  }

  build() {
    Stack() {
      Image($r('app.media.ic_launcher_background'))
        .width(CommonConstants.FULL_WIDTH)
        .height(CommonConstants.FULL_HEIGHT)
      Column() {
        Image($r('app.media.ic_logo'))
          .width($r('app.float.launcher_logo_size'))
          .height($r('app.float.launcher_logo_size'))
          .margin({ top: CommonConstants.LAUNCHER_IMAGE_MARGIN_TOP })
        Text($r('app.string.healthy_life_text'))
          .width($r('app.float.launcher_life_text_width'))
          .height($r('app.float.launcher_life_text_height'))
          .healthyLifeTextStyle(FontWeight.Bold,
            CommonConstants.LAUNCHER_LIFE_TEXT_SPACING,
            $r('app.float.launcher_text_title_size'),
            $r('app.color.launcher_text_title_color'))
          .margin({ top: CommonConstants.LAUNCHER_TEXT_TITLE_MARGIN_TOP })
        Text($r('app.string.healthy_life_introduce'))
          .height(CommonConstants.LAUNCHER_TEXT_INTRODUCE_HEIGHT)
          .healthyLifeTextStyle(FontWeight.Normal,
            CommonConstants.LAUNCHER_TEXT_INTRODUCE_SPACING,
            $r('app.float.launcher_text_introduce_size'),
            $r('app.color.launcher_text_introduce_color'))
          .opacity($r('app.float.launcher_text_opacity'))
          .margin({ top: CommonConstants.LAUNCHER_TEXT_INTRODUCE_MARGIN_TOP })
      }
      .height(CommonConstants.FULL_HEIGHT)
      .width(CommonConstants.FULL_WIDTH)
    }
  }
}
// Healthy living text common styles.
@Extend(Text) function healthyLifeTextStyle(fontWeight: number,
                                            textAttribute: number, fontSize: Resource, fontColor: Resource) {
  .fontWeight(fontWeight)
  .letterSpacing(textAttribute)
  .fontSize(fontSize)
  .fontColor(fontColor)
}```

弹窗代码


import router from '@ohos.router';
import Logger from '../common/utils/Logger';
import CommonConstants from '../common/constants/CommonConstants';
import { GlobalContext } from '../common/utils/GlobalContext';

/**
 * Custom pop-up window.
 */
@CustomDialog
@Preview
export default struct CustomDialogComponent {
  controller: CustomDialogController = new CustomDialogController({'builder': ''});
  cancel: Function = () => {};
  confirm: Function = () => {};

  build() {
    Column() {
      Text($r('app.string.dialog_text_title'))
        .width(CommonConstants.DIALOG_COMPONENT_WIDTH_PERCENT)
        .fontColor($r('app.color.dialog_text_color'))
        .fontSize($r('app.float.dialog_text_privacy_size'))
        .textAlign(TextAlign.Center)
        .fontWeight(CommonConstants.DIALOG_TITLE_FONT_WEIGHT)
        .margin({
          top: $r('app.float.dialog_text_privacy_top'),
          bottom: $r('app.float.dialog_text_privacy_bottom')
        })
      Text($r('app.string.dialog_text_privacy_content'))
        .fontSize($r('app.float.dialog_common_text_size'))
        .width(CommonConstants.DIALOG_COMPONENT_WIDTH_PERCENT)
      Text($r('app.string.dialog_text_privacy_statement'))
        .width(CommonConstants.DIALOG_COMPONENT_WIDTH_PERCENT)
        .fontColor($r('app.color.dialog_text_statement_color'))
        .fontSize($r('app.float.dialog_common_text_size'))
        .onClick(() => {
          // globalThis.isJumpPrivacy = true;
          GlobalContext.getContext().setObject('isJumpPrivacy', true);
          router.pushUrl({
          //此处和web交互,只能真机显示链接,这个页面自己写吧
            url: CommonConstants.PRIVACY_PAGE_URL
          }).catch((error: Error) => {
            Logger.error(CommonConstants.CUSTOM_DIALOG_TAG, 'CustomDialog pushUrl error ' + JSON.stringify(error));
          });
        })
      Text($r('app.string.dialog_text_declaration_prompt'))
        .width(CommonConstants.DIALOG_COMPONENT_WIDTH_PERCENT)
        .fontColor($r('app.color.dialog_text_color'))
        .fontSize($r('app.float.dialog_common_text_size'))
        .opacity($r('app.float.dialog_text_opacity'))
        .margin({ bottom: $r('app.float.dialog_text_declaration_bottom') })
      Row() {
        Text($r('app.string.dialog_button_disagree'))
          .fancy()
          .onClick(() => {
            this.controller.close();
            this.cancel();
          })
        Blank()
          .backgroundColor($r('app.color.dialog_blank_background_color'))
          .width($r('app.float.dialog_blank_width'))
          .height($r('app.float.dialog_blank_height'))
        Text($r('app.string.dialog_button_agree'))
          .fancy()
          .onClick(() => {
            this.controller.close();
            this.confirm();
          })
      }
      .margin({ bottom: CommonConstants.DIALOG_ROW_MARGIN_BOTTOM })
    }
    .width(CommonConstants.DIALOG_WIDTH_PERCENT)
    .borderRadius(CommonConstants.DIALOG_BORDER_RADIUS)
    .backgroundColor(Color.White)
  }
}

// Common text styles.
@Extend(Text) function fancy () {
  .fontColor($r("app.color.dialog_fancy_text_color"))
  .fontSize($r("app.float.dialog_fancy_text_size"))
  .textAlign(TextAlign.Center)
  .fontWeight(FontWeight.Medium)
  .layoutWeight(CommonConstants.COMMON_LAYOUT_WEIGHT)
}

资源文件引用


export default class CommonConstants {
  /**
   * The main ability tag.
   */
  static readonly ENTRY_ABILITY_TAG: string = 'EntryAbility';

  /**
   * The launcher page tag.
   */
  static readonly LAUNCHER_PAGE_TAG: string = 'LauncherPage';

  /**
   * The advertsing page tag.
   */
  static readonly ADVERTISING_PAGE_TAG: string = 'AdvertisingPage';

  /**
   * The custom dialog component tag.
   */
  static readonly CUSTOM_DIALOG_TAG: string = 'CustomDialogComponent';

  /**
   * Preference saved key.
   */
  static readonly PREFERENCES_KEY_PRIVACY: string = 'isPrivacy';

  /**
   * Preference saved file name.
   */
  static readonly PREFERENCES_FILE_NAME: string = 'myStore';

  /**
   * Launcher page count down.
   */
  static readonly LAUNCHER_DELAY_TIME: number = 2000;

  /**
   * Image logo top margin.
   */
  static readonly LAUNCHER_IMAGE_MARGIN_TOP: string = '16.2%';

  /**
   * Healthy living text spacing.
   */
  static readonly LAUNCHER_LIFE_TEXT_SPACING: number = 0.1;

  /**
   * Healthy living title text top margin.
   */
  static readonly LAUNCHER_TEXT_TITLE_MARGIN_TOP: string = '0.5%';

  /**
   * Content control height.
   */
  static readonly LAUNCHER_TEXT_INTRODUCE_HEIGHT: string = '2.7%';

  /**
   * Healthy living instructions.
   */
  static readonly LAUNCHER_TEXT_INTRODUCE_SPACING: number = 3.4;

  /**
   * Healthy living content top margin.
   */
  static readonly LAUNCHER_TEXT_INTRODUCE_MARGIN_TOP: string = '1.3%';

  /**
   * Interval execution time.
   */
  static readonly ADVERTISING_INTERVAL_TIME: number = 1000;

  /**
   * Advertising page url.
   */
  static readonly ADVERTISING_PAGE_URL: string = 'pages/AdvertisingPage';

  /**
   * Display countdown seconds.
   */
  static readonly ADVERTISING_COUNT_DOWN_SECONDS: number = 5;

  /**
   * Count down text spacing.
   */
  static readonly ADVERTISING_TITLE_TEXT_LETTER_SPACING: number = 0.05;

  /**
   * Advertising page healthy text spacing.
   */
  static readonly ADVERTISING_HEALTHY_LIFE_TEXT_SPACING: number = 0.1;

  /**
   * Advertising page health description text spacing.
   */
  static readonly ADVERTISING_TEXT_INTRODUCE_LETTER_SPACING: number = 3.4;

  /**
   * Advertising page health description text top margin.
   */
  static readonly ADVERTISING_TEXT_INTRODUCE_MARGIN_TOP: string = '0.4%';

  /**
   * Column container left margin.
   */
  static readonly ADVERTISING_COLUMN_MARGIN_LEFT: string = '3.1%';

  /**
   * Row container bottom margin.
   */
  static readonly ADVERTISING_ROW_MARGIN_BOTTOM: string = '3.1%';

  /**
   * Dialog component width the percentage of the 90.
   */
  static readonly DIALOG_COMPONENT_WIDTH_PERCENT: string = '90%';

  /**
   * Dialog title text weight.
   */
  static readonly DIALOG_TITLE_FONT_WEIGHT: number = 600;

  /**
   * Dialog width the percentage of the 93.3.
   */
  static readonly DIALOG_WIDTH_PERCENT: string = '93.3%';

  /**
   * Dialog border radius.
   */
  static readonly DIALOG_BORDER_RADIUS: number = 24;

  /**
   * Dialog component bottom margin,
   */
  static readonly DIALOG_ROW_MARGIN_BOTTOM: string = '3.1%';

  /**
   * Dialog y-axis offset distance.
   */
  static readonly DIALOG_CONTROLLER_DY_OFFSET: number = -24;

  /**
   * Width the percentage of the 100.
   */
  static readonly FULL_WIDTH: string = '100%';

  /**
   * Height the percentage of the 100.
   */
  static readonly FULL_HEIGHT: string = '100%';

  /**
   * Privacy page url.
   */
  static readonly PRIVACY_PAGE_URL: string = 'pages/PrivacyPage';

  /**
   * App home page url.
   */
  static readonly APP_HOME_PAGE_URL: string = 'pages/AppHomePage';

  /**
   * Common layout weight.
   */
  static readonly COMMON_LAYOUT_WEIGHT: number = 1;
}

资源文件引用

{
  "string": [
    {
      "name": "module_desc",
      "value": "模块描述"
    },
    {
      "name": "EntryAbility_desc",
      "value": "主程序入口"
    },
    {
      "name": "home_page_text",
      "value": "Hello World"
    },
    {
      "name": "EntryAbility_label",
      "value": "应用首次启动"
    },
    {
      "name": "healthy_life_text",
      "value": "健康生活"
    },
    {
      "name": "healthy_life_introduce",
      "value": "健康总有新玩法"
    },
    {
      "name": "advertising_text_title",
      "value": "跳过广告 %ds"
    },
    {
      "name": "dialog_text_title",
      "value": "欢迎使用我的应用"
    },
    {
      "name": "dialog_text_privacy_content",
      "value": "我们充分尊重用户的隐私权,并按照法律要求和业界成熟的安全标准,为您的个人信息提供相应的安全保护措施。"
    },
    {
      "name": "dialog_text_privacy_statement",
      "value": "隐私协议保护声明"
    },
    {
      "name": "dialog_text_declaration_prompt",
      "value": "(以下简称为“本声明”)以便您了解我们如何搜集、使用、披露、保护、存储及传输您的个人数据。请您仔细阅读本声明。如您有任何疑问,请告知我们。"
    },
    {
      "name": "dialog_button_disagree",
      "value": "不同意"
    },
    {
      "name": "dialog_button_agree",
      "value": "同 意"
    },
    {
      "name": "privacy_text_title",
      "value": "隐私政策"
    },
    {
      "name": "privacy_text_content",
      "value": "本声明的重要组成部分,服务如何处理您的个人信息,您如何行使您的数据主体权利,相关的数据控制者以及如何联系该数据控制者等基本情况,由该隐私通知进行阐述。若隐私通知与本声明之间存在不一致,以该隐私通知为准,隐私通知中未约定的,以本声明为准。"
    },
    {
      "name": "privacy_back",
      "value": "返回"
    }
  ]
}

广告页展示,里面显示的图片和倒计时,可行更改



import router from '@ohos.router';
import Logger from '../common/utils/Logger';
import CommonConstants from '../common/constants/CommonConstants';


@Entry
@Component
struct AdvertisingPage {
  @State countDownSeconds: number = CommonConstants.ADVERTISING_COUNT_DOWN_SECONDS;
  private timeId: number = 0;

  onPageShow() {
    this.timeId = setInterval(() => {
      if (this.countDownSeconds === 0) {
        this.jumpToAppHomePage();
      } else {
        this.countDownSeconds--;
      }
    }, CommonConstants.ADVERTISING_INTERVAL_TIME);
  }

  onPageHide() {
    router.clear();
    clearInterval(this.timeId);
  }

  /**
   * Jump to app home page.
   * 首页内容请看下一篇文章。
   */
  jumpToAppHomePage() {
    router.pushUrl({
      url: CommonConstants.APP_HOME_PAGE_URL
    }).catch((error: Error) => {
      Logger.error(CommonConstants.ADVERTISING_PAGE_TAG, 'AdvertisingPage pushUrl error ' + JSON.stringify(error));
    });
  }

  build() {
    Stack({ alignContent: Alignment.Top }) {
      Image($r('app.media.ic_advertising_background'))
        .width(CommonConstants.FULL_WIDTH)
        .height(CommonConstants.FULL_HEIGHT)
      Text($r('app.string.advertising_text_title', this.countDownSeconds))
        .fontColor(Color.White)
        .fontSize($r('app.float.advertising_text_font_size'))
        .letterSpacing(CommonConstants.ADVERTISING_TITLE_TEXT_LETTER_SPACING)
        .backgroundColor($r('app.color.advertising_text_background_color'))
        .border({
          radius: $r('app.float.advertising_text_radius'),
          width: $r('app.float.advertising_text_border_width'),
          color: Color.White
        })
        .margin({
          top: $r('app.float.advertising_title_text_margin_top'),
          left: $r('app.float.advertising_title_text_margin_left')
        })
        .padding({
          left: $r('app.float.advertising_text_padding_left'),
          top: $r('app.float.advertising_text_padding_top'),
          right: $r('app.float.advertising_text_padding_left'),
          bottom: $r('app.float.advertising_text_padding_bottom')
        })
        .onClick(() => {
          this.jumpToAppHomePage();
        })
      Row() {
        Image($r('app.media.ic_logo'))
          .width($r('app.float.advertising_image_width'))
          .height($r('app.float.advertising_image_height'))
          .margin({ bottom: CommonConstants.ADVERTISING_ROW_MARGIN_BOTTOM })
        Column() {
          Text($r('app.string.healthy_life_text'))
            .bottomTextStyle(FontWeight.Bolder,
            CommonConstants.ADVERTISING_HEALTHY_LIFE_TEXT_SPACING,
              $r('app.float.advertising_text_title_size'),
              $r('app.color.advertising_text_title_color'))
          Text($r('app.string.healthy_life_introduce'))
            .bottomTextStyle(FontWeight.Normal,
            CommonConstants.ADVERTISING_TEXT_INTRODUCE_LETTER_SPACING,
              $r('app.float.advertising_text_introduce_size'),
              $r('app.color.launcher_text_introduce_color'))
            .opacity($r('app.float.advertising_text_opacity'))
            .margin({ top: CommonConstants.ADVERTISING_TEXT_INTRODUCE_MARGIN_TOP })
        }
        .alignItems(HorizontalAlign.Start)
        .margin({
          left: CommonConstants.ADVERTISING_COLUMN_MARGIN_LEFT,
          bottom: CommonConstants.ADVERTISING_ROW_MARGIN_BOTTOM
        })
      }
      .alignItems(VerticalAlign.Bottom)
      .height(CommonConstants.FULL_HEIGHT)
    }
    .width(CommonConstants.FULL_WIDTH)
    .height(CommonConstants.FULL_HEIGHT)
  }
}
// Bottom text common style.
@Extend(Text) function bottomTextStyle (fontWeight: number,
  textAttribute: number, fontSize: Resource, fontColor: Resource) {
    .fontWeight(fontWeight)
    .letterSpacing(textAttribute)
    .fontSize(fontSize)
    .fontColor(fontColor)
}

资源文件引用

开屏页,每个app启动时的第一个页面,第一次安装时会显示让用户查看的隐私协议弹窗

轻量级缓存框架Preferences使用转载来源如下

转载来源

因为这个页面用到了数据存储方面的知识,所以详细介绍和使用如下。

一、介绍
Preferences 首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。

数据存储形式为键值对,键的类型为字符串型,值的存储数据类型包括数字型、字符型、布尔型以及这3种类型的数组类型。具体参考官网。

二、使用
1、导入模块
import DataPreferences from ‘@ohos.data.preferences’;
2、获取实例

let preferences = DataPreferences.getPreferences(context, name);
preferences .then((res) => {
//res 就是返回的实例
})
.catch(reason => {
Logger.error(this.TAG, ‘获取Preferences实例失败’);
})
});```

getPreferences 方法是一个异步调用过程,返回的是Promise对象,实际需要使用then语句进行回调处理,入参context是上下文环境,name是配置文件的名称

3、写入数据
let preferences = DataPreferences.getPreferences(this.context, name);
preferences.then((res) => {
res.put(IS_PRIVACY, true).then(() => {
res.flush();
Logger.info(‘PutData’,‘isPrivacy is put success’);
}).catch((err) => {
Logger.info(‘PutData’,‘isPrivacy put failed. Cause:’ + err);
});
})

在获取到实例后,使用put方法进行数据写入,这里写入一个boolean 值,key名称为IS_PRIVACY,put完成后,需要调用flush方法,将数据实际的写入到文件中。

4、读取数据
let preferences = DataPreferences.getPreferences(this.context, name);
preferences.then((res) => {
res.get(IS_PRIVACY, false).then((isPrivate) => {
Logger.info(‘GetData’,‘isPrivacy is get success, isPrivate:’ + isPrivate);
});
});
三、简单封装
1、封装
实际项目使用中,每次需要导入**@ohos.data.preferences**模块,获取实例,判断实例获取成功后,再进行数据的写入或者读取,这样稍微繁琐了点,这里对preferences 进行一个简单的封装,也方便管理preferences,后续替换底层实现也会少一些工作投入。

这里我们定义一个工具类 PreferencesUtils.ts,

/**

  • 轻量级缓存工具类
    */

import DataPreferences from ‘@ohos.data.preferences’;
import { Logger } from ‘./log/Logger’;

export class PreferencesUtils {
private static readonly TAG = ‘PreferencesUtils’;

private constructor() {
}

/**

  • 封装一层,增加获取失败的日志输出
  • @param context
  • @param name
    */
    public static getPreferences(context: Context, name: string): Promise<DataPreferences.Preferences> {
return new Promise<DataPreferences.Preferences>((resolved,rejected) => {
  DataPreferences.getPreferences(context, name)
    .then((res) => {
      resolved(res);
    })
    .catch(reason => {
      Logger.error(this.TAG, '获取Preferences实例失败');
      rejected(reason);
    })
});

}
}

封装了获取实例的方法,增加了获取失败的日志统一输出,其他的写入,读取方法也可以这里定义方法封装,这里就不多讲了,直接使用即可。

2、封装使用
import { PreferencesUtils } from ‘@ohos/xxxLibrary/src/main/ets/common/utils/PreferencesUtils’

@Entry
@Component
struct DemoPage {
@State message: string = ‘Hello World’
context = getContext(this) as common.UIAbilityContext

build() {

}

aboutToAppear() {
PreferencesUtils
.getPreferences(this.context, ‘store’)
.then((preferences) => {
preferences.get(‘key_is_privacy’, false).then((isPrivacy) => {
//…
});
}
}

3、异步转同步调用
上述方法使用的是异步获取的方式,如果需要同步获取,可以使用await关键字将Promise转成同步,使用async配合修饰方法搭配使用。

具体如下:

import { PreferencesUtils } from ‘@ohos/xxxLibrary/src/main/ets/common/utils/PreferencesUtils’

@Entry
@Component
struct DemoPage {
@State message: string = ‘Hello World’
context = getContext(this) as common.UIAbilityContext

build() {

}

async aboutToAppear() {

try {

  let preferences = await PreferencesUtils.getPreferences(this.context, 'store');
  if (preferences) {
    let value = await preferences.get('key_is_privacy', false);
    Logger.info('GetData', 'value:' + value);
  }

} catch (error) {
  console.log('error=' + error);
}

}

}
————————————————
轻量级缓存框架Preferences使用
版权声明:本文为CSDN博主「小枫_S」的原创文章,遵循CC 4.0 BY-SA版权协议,
原文链接:https://blog.csdn.net/q919233914/article/details/130262104
————————————————

  • 17
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值