灌篮少年 发表于 2024-11-20 23:23:42

《探索 HarmonyOS NEXT(5.0):开启构建模块化项目架构奇幻之旅 ——第三方

1、ef_rcp简介:是基于rcp封装的网络请求相关包.提供了rcp的上传,下载,post,get,cancel,delete,put等操作。
ef_rcp出处ef_rcp;
2、PullToRefresh简介:采用的装饰器有V1,V2,作者紧跟期间的潮流。功能上:实现垂直列表下拉刷新,上拉加载,横向列表左拉刷新,右拉加载等操作。
PullToRefresh出处PullToRefresh;
3、SmartDialog简介:鸿蒙版本的SmartDialog,优雅,极简的用法;非UI地域内利用,自界说Component;返回事件处理,优化的跨页面交互;多弹窗本领,多位置弹窗:上下左右中间;
定位弹窗:自动定位目的Component;极简用法的loading弹窗。
SmartDialog出处SmartDialog;
非常感谢大佬们出的库,方便我们开发,提高我们开发效率,更具体的先容和利用请看官方出处。
其它
RCP官网
RCP网络请求配置

1、增长BaseResponse。

export class BaseResponse<T> {
//成功失败标识
private success: boolean;
//返回提示信息
private msg: string;
//响应编码
private code: string | number;
//返回数据
private data: T;

/**
   * 构造函数
   * @param success是否成功标识
   * @param msg   提示消息
   * @param data数据
   */
constructor(success: boolean, msg: string, data: T, code: string | number) {
    this.msg = msg;
    this.success = success;
    this.code = code;
    this.data = data;
}

/**
   * 创建空实例
   * @returns
   */
static create(): BaseResponse<string> {
    let baseResponse = new BaseResponse<string>(true, '', '', 200);
    return baseResponse;
}


/**
   * 成功-只包含消息
   * @param msg   提示消息
   * @returns
   */
static OK(msg: string): BaseResponse<string> {
    let baseResponse = new BaseResponse<string>(true, msg, '', 200);
    return baseResponse;
}

/**
   * 成功-包含单行数据
   * @param msg提示消息
   * @param data单行数据
   * @returns
   */
static OKByData<T>(msg: string, data: T): BaseResponse<T> {
    let dto = new BaseResponse<T>(true, msg, data, 200);
    return dto;
}


/**
   * 失败-包含提示消息
   * @param msg 提示消息
   * @returns
   */
static Error(msg: string): BaseResponse<string> {
    let dto = new BaseResponse<string>(false, msg, '', 400);
    return dto;
}

/**
   * 失败-包含单行数据
   * @param msg 提示消息
   * @param data 单行数据
   * @returns
   */
static ErrorByData<T>(msg: string, data: T): BaseResponse<T> {
    let dto = new BaseResponse<T>(false, msg, data, 400);
    return dto;
}

public setSuccess(success: boolean) {
    this.success = success;
}

public getSuccess(): boolean {
    return this.success;
}


public getCode(): string | number {
    return this.code;
}

public setCode(code: string | number) {
    this.code = code;
}


public getMsg(): string {
    return this.msg;
}

public setMsg(msg: string) {
    this.msg = msg;
}


public getData(): T {
    return this.data;
}

public setData(data: T) {
    this.data = data;
}
}
2、增长不同接口返回不同数据结构的响应内容拦截器,保持输出划一。

官方拦截器链接
2.1.思量几种常见数据返回的
// {"code":200,"message":"success","data":{"isCheck":"0"}}
// {"status":"1","msg":"success","result":{"data":{"ticket":"ST-2c2fd56bad7f49259edbb4b00ccc6cbe"}}}
2.2.修改响应内容
https://i-blog.csdnimg.cn/direct/1456a82f1d27486eaf047bf064d83a25.png


[*]先获取响应效果
const response = await next.handle(context);


[*]把接口返回不同的code,msg,data转为划一。例子中code为200和0代表成功,可根据公司规定修改。
[*]思量几种常见数据返回的
      // {"code":200,"message":"success","data":{"isCheck":"0"}}
      // {"status":"1","msg":"success","result":{"data":{"ticket":"xxxxx"}}}
      // {"data": ..., "errorCode": 0, "errorMsg": ""} errorCode = 0 代表执行成功.errorCode = -1001 代表登录失效,需要重新登录
//转换成object
      let result = response.toJSON();
//考虑几种常见数据返回的
      // {"code":200,"message":"success","data":{"isCheck":"0"}}
      // {"status":"1","msg":"success","result":{"data":{"ticket":"ST-2c2fd56bad7f49259edbb4b00ccc6cbe"}}}
      // {"data": ..., "errorCode": 0, "errorMsg": ""} errorCode = 0 代表执行成功.errorCode = -1001 代表登录失效,需要重新登录

      //1.成功失败标识
      let success: boolean = true;
      //2.返回的消息提示
      let message: string = '';
      //3.返回的数据
      let data: Record<string, Object> = {};
      //4.返回请求状态码
      let code = response.statusCode + '';

      if (result) {
      Object.entries(result).forEach((item: object) => {

          if (["errorCode", "code", "status"].includes(String(item))) {
            code = String(item)
            success = disposeCode(String(item));
          }
          if (["errorMsg", "message", "msg"].includes(String(item))) {
            message = String(item);
          }
          if (["data", "result"].includes(String(item))) {
            data = item;
          }
      })
      }
      // HTTP status code
      if (response.statusCode != 200) {
      success = false;
      message = (response.toString() as string) ? response.toString() as string : "网络错误";
      }


[*]成功失败标识用success表示。
if (["code", "status"].includes(String(item))) {
          code = String(item)
          success = disposeCode(String(item));
      }


[*]disposeCode的作用,处理不同接口返回的码
/** 处理不同接口返回的码.errorCode = 0 || code==200 || status==1 为成功 */
export function disposeCode(s: string): boolean {
let isSuccess: boolean = false;
if ("0" == s || "200" == s || "1" == s) {
isSuccess = true;
}
return isSuccess;
}


[*]返回统一格式
    let baseResponse = new BaseResponse(success, disposeMsg(code, message), data, code);


[*]disposeMsg的作用,表示处理信息,比如针对于特定的code码,返回指定的msg。


/** 处理信息 */
export function disposeMsg(code: string, msg: string): string {
let message = "";
switch (code) {
    case '-1001':
    case '401':
      message = "登陆信息过期,请重新登陆";
      break;
    case '403':
      message = "账号异常,请联系客服";
      break;
    case '429':
      message = "您的操作过于频繁,请稍后再试!";
      break;
    case '500':
      message = "服务器错误(500)";
      break;
    default:
      message = `${msg}`;
}
const errorCodes = ['401', '403', '429', '500'];
if (errorCodes.includes(code)) {
    //吐司
    ToastUtil.showToast(message);
}
return message;
}


[*]修改响应后,给rcp.Response传过去。
      //对响应修改
      const toReturn: rcp.Response = {
      request: response.request,
      statusCode: response.statusCode,
      headers: response.headers,
      effectiveUrl: response.effectiveUrl,
      body: response.body,
      downloadedTo: response.downloadedTo,
      timeInfo: response.timeInfo,
      httpVersion: response.httpVersion,
      reasonPhrase: response.reasonPhrase,
      toString: () => response.toString(),
      toJSON: () => baseResponse
      };
      return toReturn;
3、初始化参数配置,在EntryAbility的onWindowStageCreate写

    // if(运行环境===debug){
   efRcp
   .setBaseURL("https://www.wanandroid.com") //设置请求路径
   .enableLogInterceptor()//设置为false即可关闭
   .setLoadingContent('正在加载中...')//更改loading文本
   .convertError(true)//表示如果response.toJSON()异常时将响应字符串返回,false则表示值返回异常提醒而不返回结果
   .addCommonHeaders({//设置公共头
       "version": `V1.0.0`
   })// .setLoadingImg(wrapBuilder(loadingImg)) //设置loading为gif图片
   .addCustomInterceptors()
   .addSysCodeEvent({//添加统一系统框架级别编码处理逻辑,如超时等
       listener: (code: number) => {
         // Log.d('---------addSysCodeEvent监听事件-----' + code)
         switch (code) {
         case -1001:
         case 401:
             console.log(`xxx : ---跳转登录页面,清空一些数据等`)
             //跳转登录页面,清空一些数据等
             // ZRouter.replacePathByName('LoginPage')
             break;
         }
       }
   })//创建session对象,需要再设置为一系列操作后再调用,否则设置不生效,可在特殊情况处设置其他操作后重新创建session
   .create()//获取统一的session对象,必须在create后调用 .设置了全局的配置比如拦截器超时时间session本身的配置 ,需要再次create
   .builder();
   // }

4、如何利用,更多具体内容可以参考ef_rcp作者的主页



[*]post请求为例,例子在 TestNetView 页面
//登录
          let login = await PhoneService.login({
            'username': '鸿蒙',
            'password': '5.0'
          })
          console.log('xxxlogin--' + '' + json.stringify(login))
          if (login.data?.getSuccess()) {
            this.message = json.stringify(login.data.getData())
            console.log('xxxxxdata参数--' + '' + json.stringify(login.data.getData()))
          } else {
            console.log('xxx--' + login.data?.getMsg())
            ToastUtil.showToast(login.data?.getMsg())
          }
         
PhoneService类
export class PhoneService {
static login(query: Record<string, Object>): Promise<EfRcpResponse<BaseResponse<object>>> {
    return efRcpClientApi.post<BaseResponse<object>>({
      url: 'user/login',
      query: query,
      loadingTxt: '正在登录中...',
      loading: false
    });
}
}
https://i-blog.csdnimg.cn/direct/76933129c5f24a32a8f5a8bcc85b38c4.png
5、自带的loading( 图一 ) 假如不符合可以自界说一个builder,参考下面的弹窗设置。

https://i-blog.csdnimg.cn/direct/f1027e6c71414e3a8e34f576458f0c12.png


[*]设置自界说loading样式和内容
.setLoadingBuilder(wrapBuilder(loadingNetView))

/** 目前网络请求不支持传loading文字 */
@Builder
function loadingNetView() {
Column() {
    loadingBuilder()
}
.border({
    width: 1,
    radius: 8,
    color: $r('app.color.color_F0F0F0')
})
}

@Builder
function loadingView(args: ResourceStr = '正在加载中...') {
loadingBuilder(args)
}

@Builder
function loadingBuilder(args?: ResourceStr) {
Column() {
    Image($r('app.media.loading_refresh')).width(50).height(50)
    Text(args ? args : '正在加载中...')
      .fontSize(11)
      .fontColor($r('app.color.color_222222'))
      .margin({ top: 11 })
      .maxLines(1)
      .textOverflow({ overflow: TextOverflow.Ellipsis })

}
.width(120)
.height(120)
.justifyContent(FlexAlign.Center)
.backgroundColor(Color.White)
.borderRadius(12)
}
https://i-blog.csdnimg.cn/direct/eac3a77a1cd84af5b877f210922dc657.png
PullToRefresh上下拉刷新配置,采用V2装饰器版本

1、下载安装,目前版本是 “^2.0.1”

ohpm install @zhongrui/pull_to_refresh_v2
2、每个项目的上下拉刷新头部,尾部都不一样,以是我这里采用了个性化定制,请利用RefreshLayout



[*]上下拉刷新例子–>TestRefreshView
@Preview
@ComponentV2
export struct TestRefreshView {}


[*]自界说刷新头部
https://i-blog.csdnimg.cn/direct/96b2b04b01324a8d851aedfe70dbd1f4.png
@Builder
defHeaderView() {
    Stack({ alignContent: Alignment.Center }) {
      if (this.pullDown?.status == PullStatus.PullDown || this.pullDown?.status == PullStatus.Refresh || this.pullDown?.status == PullStatus.PreRefresh) {
      Row() {
          Image($r("app.media.loading_refresh")).width(50).height(40)
      }.width('100%')
      .height(50)
      .justifyContent(FlexAlign.Center)
      } else if (this.pullDown?.status == PullStatus.RefreshSuccess) {
      // Text("刷新成功").textExtend()
      } else if (this.pullDown?.status == PullStatus.RefreshError) {
      Text("刷新失败,请重新尝试").textExtend()
      }

    }.height(50).width("100%")

}


[*]自界说刷新尾部
https://i-blog.csdnimg.cn/direct/8728ad7f960c4e92943af4a39a39a4e5.png
/** 自定义刷新尾部 */
@Builder
defFooterView() {
    Stack({ alignContent: Alignment.Center }) {
      if (this.pullDown?.status == PullStatus.PullUp || this.pullDown?.status == PullStatus.Load || this.pullDown?.status == PullStatus.PreLoad) {
      Row() {
          Image($r("app.media.loading_refresh"))
            .width(24)
            .height(24)
      }.width('100%')
      .height(60)
      .justifyContent(FlexAlign.Center)
      } else if (this.pullDown?.status == PullStatus.LoadSuccess) {
      // Text("加载完成").textExtend()
      } else if (this.pullDown?.status == PullStatus.LoadError) {
      Text('加载失败,请重新尝试').textExtend()
      }

    }.height(50).width("100%")

}


[*]内容视图
https://i-blog.csdnimg.cn/direct/ada2d055d7f64056985c948c2cc0df0b.png
/** 内容视图 */
@Builder
_ContentView() {
    Text("contentView:()=>{your @Builder View}")
}


[*]loading视图
https://i-blog.csdnimg.cn/direct/53bcfc89ea824bf7805e95ff51d92d49.png
/** loading视图 */
@Builder
loadingView() {
    this.layout($r('app.media.loading_refresh'), '正在加载...')
}


[*]空视图
https://i-blog.csdnimg.cn/direct/0c053421d93e4972ab60dbf4fd788767.png
/** 空视图 */
@Builder
_EmptyView() {
    this.layout($r('app.media.nodata'), '暂无数据')
}


[*]错误视图
https://i-blog.csdnimg.cn/direct/2d0123f388644847849d833c9ab9cd76.png
/** 错误视图 */
@Builder
_ErrorView() {
    this.layout($r('app.media.no_network'), '暂无网络,请重新尝试')
}


[*]视图公用
/** 视图公用 */
@Builder
layout(src: ResourceStr, text: ResourceStr) {
    Column() {
      Image(src).width(48).height(48)
      Text(text).fontSize(12).fontColor($r('app.color.color_999999')).padding(8)
    }.width(CommonConst.FULL_PARENT)
    .height(CommonConst.FULL_PARENT)
    .justifyContent(FlexAlign.Center)
}


[*]底部暂无更多(目前只能往上拉才能看到,假如不上拉也能看到,那就自界说一个Item放到最下面)。
https://i-blog.csdnimg.cn/direct/efbfb25096a34ceba599d7b8ae900314.png
/**
   * 暂无更多
   * @param isV 是否竖向
   */
@Builder
defFooterNoMoreView(isV: boolean) {
    Text('没有更多数据了')
      .textAlign(TextAlign.Center)
      .height(isV ? 50 : CommonConst.FULL_PARENT)
      .width(isV ? CommonConst.FULL_PARENT : 50)
      .fontSize(14)
      .fontColor($r('app.color.color_999999'))
}

private isVerticalLayout(): boolean {
    return (this.config.isVertical || this.config.horizontalMode == 1 || this.config.horizontalMode == 2)
}
}


[*]默认带弹性效果的列表必要关闭弹性滑动
.edgeEffect(EdgeEffect.None)


[*]BasicDataSource,配合LazyForEach利用,数据太多的话建议用LazyForEach;后期可以看看(Repeat:子组件复用)
源代码请看项目中,模块uicomponents /src/main/ets/components/refresh/BasicDataSource
3、封装请求返回的数据,更加简洁



[*]如何利用

/** 请求接口 */
async loadData() {
    let data: Array<string> = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"]
    // let data:string[] = []// 空数据
    this.dataSource.pageRefresh(true, this.pageNum, data.length, data, this.controller,
      (pages: number) => {
      this.pageNum = pages
      })

}
/**
   * 列表页数 处理
   * @param success成功失败标识
   * @param page   页数
   * @param total    总条数
   * @param data   数据
   * @param controller RefreshController
   * @param pageNums 返回的page
   */
public pageRefresh(success: boolean, page: number, total: number, data: Array<T>, controller: RefreshController, pageNums: (pages: number) => void) {
    if (success) {
      if (page == 1) {
      this.setNewData(data)
      controller.refreshSuccess()
      pageNums(2)

      if (this.totalCount() == 0) {
          controller.viewEmpty()
      }
      } else {
      this.addAllData(data)
      controller.loadSuccess()
      pageNums(data.length == 0 ? page : (page + 1))
      }
      // 是否还有更多
      setTimeout(() => {
      controller.hasMore(this.totalCount() < total /*|| !(data.length < 10)*/)
      }, 800) //处理暂无更多数据视图和列表同时显示出来


    } else {
      if (controller.getStatus() == PullStatus.Refresh) {
      controller.refreshError()
      } else {
      controller.loadError()
      }
      if (page == 1) {
      controller.viewError()
      }
    }

}

SmartDialog弹窗配置



[*]更多细节请参考大佬写的
安装

ohpm install ohos_smart_dialog
https://i-blog.csdnimg.cn/direct/3b02ad54fda9449880462dbbfc090415.png
全局设置一个loadding样式



[*]Index页面
OhosSmartDialog({ loadingBuilder: customLoading })
/** 自定义页面弹窗 */
@Builder
function customLoading(args: ResourceStr = '正在加载中...') {
Column() {
    Image($r('app.media.loading_refresh')).width(50).height(50)
    Text(args)
      .fontSize(11)
      .fontColor($r('app.color.color_222222'))
      .margin({ top: 11 })
      .maxLines(1)
      .textOverflow({ overflow: TextOverflow.Ellipsis })

}
.width(120)
.height(120)
.justifyContent(FlexAlign.Center)
.backgroundColor(Color.White)
.borderRadius(12)
}

https://i-blog.csdnimg.cn/direct/6da46a57b45940b4bca245f659679f12.png


[*]路由子页面一定要加,返回事件监听
.onBackPressed(OhosSmartDialog.onBackPressed())
以往系列文章


[*] 《探索 HarmonyOS NEXT(5.0):开启构建模块化项目架构奇幻之旅 —— 模块化基础篇》
[*]《探索 HarmonyOS NEXT(5.0):开启构建模块化项目架构奇幻之旅 —— 构建基础特性层》
[*]《探索 HarmonyOS NEXT(5.0):开启构建模块化项目架构奇幻之旅 —— 构建公共本领层》
[*]《探索 HarmonyOS NEXT(5.0):开启构建模块化项目架构奇幻之旅 —— Tabs底部导航栏》
[*]《探索 HarmonyOS NEXT (5.0):开启构建模块化项目架构奇幻之旅 —— 动态路由 ZRouter:引领高效模块通讯的聪明中枢》
若本文对您稍有帮助,诚望您不吝点赞,多谢。
有爱好的同学可以点击查看源码



[*]gitee:https://gitee.com/jiaojiaoone/explore-harmony-next.git
[*]github:https://github.com/JasonYinH/ExploreHarmonyNext.git
欢迎加我微信一起交换:+V:yinshiyuba


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 《探索 HarmonyOS NEXT(5.0):开启构建模块化项目架构奇幻之旅 ——第三方