鸿蒙next 实战项目 Boss直聘完整版来了

2,340 阅读5分钟

前言:

之前写过一个demo 但是那个是本地数据 而且也没有适配鸿蒙next版本 现在我改成网络的 接口是我本地服务的 就想着分享给大家 希望大家能够很好的去学习和工作。

效果图:

  • 桌面icon 效果

image.png

  • 欢迎页面

image.png

  • 登录页面

image.png

  • 注册页面

image.png

  • 职位模块

image.png

  • 公司模块

image.png

  • 公司详情页面

image.png

  • 消息模块

image.png

  • 消息聊天详情页面

image.png

  • 自动AI回复效果

image.png

  • 人中心模块

image.png

  • 发布职位信息模块

image.png

具体实现:

添加权限

image.png

启动本地服务

拿到项目里面的bosstabservice 配置好数据库 启动服务 image.png

客户端实现;

  • 1 底部导航器实现
import CompanylistComponent from '../view/CompanylistComponent';
import MessageListComponent from '../view/MessageListComponent';
import My from '../view/My';
import PositionListComponent from '../view/PositionListComponent';

@Entry
@Component
struct ListIndex {
 private controller: TabsController = new TabsController()
 @State SelectPos:number=0;
 public IndexClick(){
   this.SelectPos=0;
   this.controller.changeIndex(0)
 }
 public messageClick(){
   this.SelectPos=1;
   this.controller.changeIndex(1)
 }
 public myClick(){
   this.SelectPos=2;
   this.controller.changeIndex(2)
 }

 public meClick(){
   this.SelectPos=3;
   this.controller.changeIndex(3)
 }

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

     Tabs({ controller: this.controller }) {
       TabContent() {
         PositionListComponent()
       }
       TabContent() {
         CompanylistComponent()
       }
       TabContent() {
          MessageListComponent()
      }
       TabContent() {
         My()
       }
     }
     .scrollable(false)
     .barHeight(0)
     .animationDuration(0)

     Row() {
       Column(){
         Image((this.SelectPos==0?$r('app.media.ic_main_tab_company_pre'):$r('app.media.ic_main_tab_company_nor')))
           .width(30).height(30)
         Text('职位')
           .size({ width: '100%', height: 30 }).textAlign(TextAlign.Center)
           .fontSize(15)
           .fontColor((this.SelectPos==0?Color.Green:Color.Black))
       }
       .layoutWeight(1)
       .backgroundColor($r('app.color.gray2'))
       .height("100%")
       .onClick(this.IndexClick.bind(this))

       Column(){
         Image((this.SelectPos==1?$r('app.media.ic_main_tab_find_pre'):$r('app.media.ic_main_tab_find_nor')))
           .width(30).height(30)
         Text('公司')
           .size({ width: '100%', height: 30 }).textAlign(TextAlign.Center)
           .fontSize(15)
           .fontColor((this.SelectPos==1?Color.Green:Color.Black))
       }
       .layoutWeight(1)
       .backgroundColor($r('app.color.gray2'))
       .height("100%")
       .onClick(this.messageClick.bind(this))

       Column(){
         Image((this.SelectPos==2?$r('app.media.ic_main_tab_contacts_pre'):$r('app.media.ic_main_tab_contacts_nor')))
           .width(30).height(30)
         Text('消息')
           .size({ width: '100%', height: 30 }).textAlign(TextAlign.Center)
           .fontSize(15)
           .fontColor((this.SelectPos==2?Color.Green:Color.Black))
       }
       .layoutWeight(1)
       .backgroundColor($r('app.color.gray2'))
       .height("100%")
       .onClick(this.myClick.bind(this))

       Column(){
         Image((this.SelectPos==3?$r('app.media.ic_main_tab_my_pre'):$r('app.media.ic_main_tab_my_nor')))
           .width(30).height(30)
         Text('我的')
           .size({ width: '100%', height: 30 }).textAlign(TextAlign.Center)
           .fontSize(15)
           .fontColor((this.SelectPos==3?Color.Green:Color.Black))
       }
       .layoutWeight(1)
       .backgroundColor($r('app.color.gray2'))
       .height("100%")
       .onClick(this.meClick.bind(this))


     }.alignItems(VerticalAlign.Bottom).width('100%').height(65).margin({top:0,right:0,bottom:10,left:0})
   }
   .width('100%')
   .height('100%')
 }
}

底部导航使用 Row组件配合 Column 组件实现底导航器 然后配置 Tabs组件实现我们上门的碎片也和底部导航器点击切换效果

职位页面实现:

初始化生命接口 网络请求

async  aboutToAppear(){
  Logger.error(this.TAG+' aboutToAppear  --- > ');
  let networkurl=CommonConstant.DISH;
  await httpRequestGet(networkurl).then((data)=>{
    console.log("data --- > "+data);
    Logger.error("登录请求回调结果 ---> " +data.toString());
    let positionmodel : PositionModel = JSON.parse(data.toString())
    this.JokeList = positionmodel.data

  });
}

解析服务端响应数据创建数据model


export class PositionModel {
  msg: string = ""
  data: Array<Positiondata> = []
  code:number=0
}

export class Positiondata {
  id: string = ""
  name: string = ""
  cname: string = ""
  size: string = ""
  salary: string = ""
  username: string = ""
  title: string = ""
}

解析数据

let positionmodel : PositionModel = JSON.parse(data.toString())
this.JokeList = positionmodel.data

网络请求工具类

import http from '@ohos.net.http';
import Logger from './Logger'
import Constants, { ContentType } from '../common/Constants';
import { connection } from '@kit.NetworkKit';
import { healthStore } from '@kit.HealthServiceKit';


export  function   httpRequestGet(url:string,params?:string){
  return httpRequest(url,http.RequestMethod.GET,params);
}
export  function   httpRequestPost(url:string,params?:string){

  return httpRequest(url,http.RequestMethod.POST,params);
}
function  httpRequest(url:string ,method:http.RequestMethod,params?:string):Promise<string>{
   let  httpRequest=http.createHttp();
   let responseResult=httpRequest.request(url,{
     method:method,
     readTimeout:Constants.HTTP_READ_TIMEOUT, //读取超时时间可选  默认 600000
     header:{
       'Content-Type':ContentType.JSON
     },
     connectTimeout:Constants.HTTP_READ_TIMEOUT, //连接超时时间  可选,默认为60000ms
     extraData:params // 请求参数

   });
   let getjson:string='';
   return responseResult.then((value:http.HttpResponse)=>{
      Logger.error("请求状态-- >"+value.responseCode);
      if(value.responseCode===200){
        Logger.error("请求成功");
        let result= `${value.result}`;
        Logger.error("请求返回数据",JSON.parse(result));
        getjson=result;
      }else{
        getjson='';
      }
     return getjson;
   }).catch(()=>{
      //httpRequest.off("headerReceive");
     httpRequest.destroy();
     return '';
   });
}

日志工具类



import hilog from '@ohos.hilog';

class Logger {
  private domain: number;
  private prefix: string;
  private format: string = '%{public}s, %{public}s';

  /**
   * constructor.
   *
   * @param Prefix Identifies the log tag.
   * @param domain Domain Indicates the service domain, which is a hexadecimal integer ranging from 0x0 to 0xFFFFF.
   */
  constructor(prefix: string = 'MyApp', domain: number = 0xFF00) {
    this.prefix = prefix;
    this.domain = domain;
  }

  debug(...args: string[]): void {
    hilog.debug(this.domain, this.prefix, this.format, args);
  }

  info(...args: string[]): void {
    hilog.info(this.domain, this.prefix, this.format, args);
  }

  warn(...args: string[]): void {
    hilog.warn(this.domain, this.prefix, this.format, args);
  }

  error(...args: string[]): void {
    hilog.error(this.domain, this.prefix, this.format, args);
  }
}

export default new Logger('HTTPS', 0xFF00)

职位页面布局实现

import { PositionData, PositionModel } from '../bean/PositionModel'
import Logger from '../utils/Logger';
import { httpRequestGet } from '../utils/OkhttpUtils';
import CommonConstant, * as commonConst from '../common/CommonConstants';
import { PullToRefresh } from '@ohos/pulltorefresh'
/***
 * 创建人:xuqing
 * 创建时间:2024年6月30日14:53:45
 * 类说明: 职位模块
 *
 *
 */
@Component
export default struct PositionList {
  private  scroller:Scroller=new Scroller();
 @State positiondata:PositionData=new PositionData();
 @State JokeList:Array<PositionData>=[];
 @State dataList:Array<PositionData>=[];
 @State curPage:number=1;
 @State pageSize:number=5;
 @State curnumber:number=1;
 @State data:Array<PositionData>=this.dataList;


 async  aboutToAppear(){
   this.getpostition(this.curPage,this.pageSize);
 }

  async  getpostition(curPagennumber:number,pageSizenumber:number ){
    let getcurPage:string='curPage=';
    let getpageSize:string='&pageSize='
    let networkurl=CommonConstant.POSITIONLINIT+getcurPage+curPagennumber+getpageSize+pageSizenumber;
    httpRequestGet(networkurl).then((data)=>{
      Logger.error("职位请求结果=--->"+ data.toString());
      let positionModel:PositionModel=JSON.parse(data.toString());
      this.JokeList=positionModel.data;
      this.dataList.push(...this.JokeList);
    });
  }
  build() {
    Column(){
     Row(){
       Text('职位')
         .fontSize(30)
         .fontColor(Color.White)
         .textAlign(TextAlign.Center)
     }.width('100%')
      .height(50)
      .backgroundColor(Color.Green)
      .justifyContent(FlexAlign.Center)
    PullToRefresh({
      // 必传项,列表组件所绑定的数据
      data: $data,
      // 必传项,需绑定传入主体布局内的列表或宫格组件
      scroller: this.scroller,
      // 必传项,自定义主体布局,内部有列表或宫格组件
      customList: () => {
        // 一个用@Builder修饰过的UI方法
        this.getlistview();
      },
      // 可选项,下拉刷新回调
      onRefresh: () => {
        return new Promise<string>((resolve, reject) => {
          // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据
          resolve('刷新成功');
          this.dataList=[];
          this.curPage=1;
          this.pageSize=5;
          this.curnumber=1;
          this.getpostition(this.curPage,this.pageSize);
        });
      },
      // 可选项,上拉加载更多回调
      onLoadMore: () => {
        return new Promise<string>((resolve, reject) => {
          // 模拟网络请求操作,请求网络2秒后得到数据,通知组件,变更列表数据
          this.curnumber++;
          this.curPage=this.curnumber;
          Logger.error(" this.curPage -- > "+ this.curPage)
          this.pageSize=5;
          this.getpostition(this.curPage,this.pageSize);
          resolve('')
        });
      },
      customLoad: null,
      customRefresh: null,
    })

    }.width('100%')
    .height('100%')
  }
  @Builder
  private  getlistview(){
    List({space:commonConst.LIST_ITEM_SPACE,scroller:this.scroller}){
      ForEach(this.dataList,(item:PositionData)=>{
        ListItem(){
          Row(){
            Column(){
              Row(){
                Text(item?.name).fontSize(14).fontColor(Color.Black).align(Alignment.Start).margin({left:12,top:5})
                Text(item?.salary).fontSize(14).fontColor(Color.Red).align(Alignment.End).margin({right:12,top:5})
              }.justifyContent(FlexAlign.SpaceBetween)
              .width('100%')
              Row(){
                Text(item?.cname)
                  .fontSize(25)
                  .margin({left:12})
              }.width('100%').alignItems(VerticalAlign.Top)
              .margin({left:12,top:25})
              Row(){
                Text(item?.username)
                  .fontColor(Color.Green)
                  .margin({left:12,top:10})
              }.width('100%').alignItems(VerticalAlign.Top)
              .margin({left:12,top:20})

            }.width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Start)


          }.height( '20%')
          .width('100%')
          .justifyContent(FlexAlign.Center)
        }
      })
    }.width(commonConst.GOODS_LIST_WIDTH) .backgroundColor('#eeeeee')
    .divider({ strokeWidth: 1, color: 0x222222 })
    .edgeEffect(EdgeEffect.None) // 必须设置列表为滑动到边缘无效果
  }
}

其他的company 公司页面 和message 消息页面和职位页面实现方式类似 我这边就不展开讲了 都是进去页面第一个生命周期进行网络请求 然后解析数据赋值给数组然后渲染在我们的list组件上面 还有分页上拉加载数据和下拉刷新数据等 都在我的实战课程里面。各位同学如果想学习更多的知识可以关注我的B站课程

课程地址

www.bilibili.com/cheese/play…

项目内容:

  • 1 常用布局组件的学习

  • 2 网络请求工具类封装

  • 3 arkui 生命周期启动流程

  • 4 日志工具类的封装

  • 5 自定义组合组件的封装

  • 6 路由导航跳转的使用

  • 7 本地地数据的缓存 以及缓存工具类的封装

  • 8 欢迎页面的实现

  • 9 登录案例和自动登录效果实现

  • 10 请求网络数据分页上拉加载 下拉刷新的实现

  • 11 list数据懒加载实现

  • 12 webview组件的使用

最后总结:

鸿蒙的 ark ui 非常类似flutter 这种声明式ui 所有的布局都是代码编写的和传统的布局和逻辑分开有些区别 刚开始上手的时候可能不适应,慢慢就习惯了 还有一点 代码嵌套 这个其实可以把每个组件抽离出来就可以解决鸿蒙的ark ui 网络部分也是类似前端的http 请求 解析json和前端也比较像 也有面向对象编程的思想, 对前端和移动端同学来说也比较好理解和学习。 今天的文章就讲到这里有兴趣的 关注我B站教程 了解更多鸿蒙开发的知识 可以关注坚果派公众号 。 谢谢