鸿蒙NEXT项目实战-百得知识库03

news2025/3/20 21:16:54

代码仓地址,大家记得点个star

IbestKnowTeach: 百得知识库基于鸿蒙NEXT稳定版实现的一款企业级开发项目案例。 本案例涉及到多个鸿蒙相关技术知识点: 1、布局 2、配置文件 3、组件的封装和使用 4、路由的使用 5、请求响应拦截器的封装 6、位置服务 7、三方库的使用和封装 8、头像上传 9、应用软件更新等https://gitee.com/xt1314520/IbestKnowTeach

Home页面开发

设计图

需求分析

Home页面一共可以分为三块部分

1、头部-最新、热门

2、搜索框

3、内容主体

搭建Home页面布局

1、头部-最新、热门

最新、热门可以用Text文本组件,下面下划线用Divider组件

// 标题栏
Row({ space: 10 }) {
  Column() {
    Text($r('app.string.article_best_new'))
      .textStyles()
    // 选中标题会出现下划线
    if (this.newDividerShow) {
      Divider().dividerStyles()
    }
  }
  .onClick(() => {
    // 展示下划线
    this.hotDividerShow = false
    this.newDividerShow = true
    // 查询最新文章数据
    this.isShow = false
    this.page = 1
    this.getArticleNewDataList(true, true)
  })

  Column() {
    Text($r('app.string.article_best_hot'))
      .textStyles()
    // 选中标题会出现下划线
    if (this.hotDividerShow) {
      Divider().dividerStyles()
    }
  }
  .onClick(() => {
    this.newDividerShow = false
    this.hotDividerShow = true
    // 查询最热门文章数据
    this.isShow = false
    this.page = 1
    this.getArticleHotDataList(true, true)
  })

}.justifyContent(FlexAlign.Start)
  .margin({ top: 10, bottom: 20 })
  .width(CommonConstant.WIDTH_FULL)

2、搜索框

搜索框可以使用鸿蒙原生组件Search

// 搜索
Search({ placeholder: '请输入关键字', value: $$this.keyword })
  .placeholderFont({ size: 14 })
  .textFont({ size: 14 })
  .onSubmit(() => {
    // 处理标题
    this.title = encodeURIComponent(this.keyword)
    // 分页查询文章内容
    this.isShow = false
    this.page = 1
    this.getArticleDataList(true)
  })
  .width(CommonConstant.WIDTH_FULL)
  .height(30)
3、内容主体

内容主体相当于我们把文章单条内容封装成一个组件,然后使用List组件进行布局嵌套使用Foreach进行循环遍历文章数组对象数据

整体大家看一条文章数据,分为三行,行使用的是Row布局,整体三行布局从上到下是列所以组件整体用Column进行包裹

封装单条文章组件(在ets下面创建component目录然后创建ArticleComponent.ets文件)

import { ArticleContentData } from '../api/ArticleContentApi.type'
import { CommonConstant } from '../contants/CommonConstant'

@Component
  export struct ArticleComponent {
    // 文章数据
    @Prop articleContentData: ArticleContentData

    build() {
      Column() {
        Row({ space: 10 }) {
          // 头像
          Image(this.articleContentData.avatarUri)
            .width($r('app.float.common_width_tiny'))
            .aspectRatio(1)
            .borderRadius(10)
          // 姓名
          Text(this.articleContentData.nickname)
            .fontSize($r('app.float.common_font_size_medium'))
            .fontColor($r('app.color.common_gray'))
            .width('70%')
            .maxLines(1)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
        }.width(CommonConstant.WIDTH_FULL)

        Row() {
          // 内容标题
          Text(this.articleContentData.title)
            .fontSize($r('app.float.common_font_size_small'))
            .width('80%')
            .maxLines(3)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
          Image(this.articleContentData.coverUrl)
            .width($r('app.float.common_width_medium'))
            .aspectRatio(1)
            .objectFit(ImageFit.Contain)
            .borderRadius(5)
        }.width(CommonConstant.WIDTH_FULL)
          .justifyContent(FlexAlign.SpaceBetween)

        // 展示阅读、点赞、收藏
        Row({ space: 3 }) {
          Text(this.articleContentData.readCount + '阅读')
            .textStyles()
          Text('|')
            .textStyles()
          Text(this.articleContentData.time)
            .textStyles()
          Text('|')
            .textStyles()
          // 文章内容标签
          if (this.articleContentData.contentCategory === '1') {
            Text('鸿蒙')
              .textLabelStyles()
          } else if (this.articleContentData.contentCategory === '2') {
            Text('Java')
              .textLabelStyles()
          } else if (this.articleContentData.contentCategory === '3') {
            Text('Web前端')
              .textLabelStyles()
          } else if (this.articleContentData.contentCategory === '4') {
            Text('运维')
              .textLabelStyles()
          } else {
            Text('未知级别')
              .textLabelStyles()
          }
          Text('|')
            .textStyles()
          // 文章难度分类标签
          if (this.articleContentData.difficultyCategory === '1') {
            Text(this.articleContentData.platformCategory === '1' ? '基础知识' : '简单面试题')
              .textLabelStyles()
          } else if (this.articleContentData.difficultyCategory === '2') {
            Text(this.articleContentData.platformCategory === '1' ? '进阶知识' : '中等面试题')
              .textLabelStyles()
          } else if (this.articleContentData.difficultyCategory === '3') {
            Text(this.articleContentData.platformCategory === '1' ? '高级知识' : '困难面试题')
              .textLabelStyles()
          } else {
            Text('未知级别')
              .textLabelStyles()
          }
        }.width(CommonConstant.WIDTH_FULL)

      }
      .padding(10)
        .width(CommonConstant.WIDTH_FULL)
        .height(110)
        .margin({ top: 13 })
    .backgroundColor($r('app.color.common_white'))
    .justifyContent(FlexAlign.SpaceBetween)
    .borderRadius(10)

  }
}

@Extend(Text)
function textStyles() {
  .fontColor($r('app.color.common_gray'))
  .fontSize($r('app.float.common_font_size_tiny'))
}

@Extend(Text)
function textLabelStyles() {
  .fontSize($r('app.float.common_font_size_tiny'))
  .fontColor(Color.Black)
}

然后我们整体在Home页面里面展示我们的文章内容数组数据

if (this.isShow) {
  Refresh({ refreshing: $$this.isRefreshing }) {
    // 内容组件
    List() {
      ForEach(this.articleContentList, (item: ArticleContentData) => {
        ListItem() {
          ArticleComponent({ articleContentData: item })
            .onClick(() => {
              // 路由到内容详情页
              router.pushUrl({
                url: RouterConstant.VIEWS_HOME_ARTICLE_INFO, params: {
                  "articleId": item.id
                }
              })
            })
        }
      })
      if (this.textShow) {
        ListItem() {
          Text($r('app.string.no_have_article'))
            .fontColor($r('app.color.common_gray'))
            .fontSize($r('app.float.common_font_size_small'))
            .width(CommonConstant.WIDTH_FULL)
            .textAlign(TextAlign.Center)
            .margin({ top: 80 })
        }
      }
    }
    .width(CommonConstant.WIDTH_FULL)
      .height(CommonConstant.HEIGHT_FULL)
      .scrollBar(BarState.Off)
      .onReachEnd(() => {
        if (!this.isLoad) {
          if (this.total > this.page * this.pageSize) {
            this.isLoad = true
            this.page++
            if (this.newDividerShow) {
              // 分页查询最新文章数据
              this.getArticleNewDataList(false, false)
            }
            if (this.hotDividerShow) {
              // 分页查询最热门文章数据
              this.getArticleHotDataList(false, false)
            }
          } else {
            this.textShow = true
          }

        }
      })
  }
  .onRefreshing(() => {
    if (!this.isLoad) {
      this.isLoad = true
      this.textShow = false
      // 页面恢复到1
      this.page = 1
      if (this.newDividerShow) {
        // 分页查询最新文章数据
        this.getArticleNewDataList(true, true)
      }
      if (this.hotDividerShow) {
        // 分页查询最热门文章数据
        this.getArticleHotDataList(true, true)
      }

    }
  })
} else {
  // 加载组件
  LoadingComponent()
}

加载组件 LoadingComponent()是我们自己进行封装的,因为我们的数据可能因为网络原因还没在服务端查询出来,界面为了不要展示长时间空白让用户误会,所以展示我们数据正在加载中

import { CommonConstant } from '../contants/CommonConstant'

@Component
  export struct LoadingComponent {
    @State message: string = '数据正在加载中'

    build() {
      Row() {
        LoadingProgress()
          .width(30).height(30).color(Color.Gray)
        Text(this.message)
          .fontSize((14)).fontColor(Color.Gray)
      }.height("80%")
        .width(CommonConstant.WIDTH_FULL)
        .justifyContent(FlexAlign.Center)
    }
  }
4、整体Home页面代码

import { router } from '@kit.ArkUI'
import { RouterConstant } from '../../contants/RouterConstant'
import { CommonConstant } from '../../contants/CommonConstant'
import { ArticleComponent } from '../../components/ArticleComponent'
import articleContentApi from '../../api/ArticleContentApi'
import { LoadingComponent } from '../../components/LoadingComponent'
import { ArticleContentData } from '../../api/ArticleContentApi.type'
import { showToast } from '../../utils/Toast'

@Entry
  @Component
  export struct Home {
    // 是否展示最新标题的下划线
    @State newDividerShow: boolean = true
    // 是否展示热门标题的下划线
    @State hotDividerShow: boolean = false
    // 搜索词
    @State keyword: string = ''
    // 标题
    @State title: string = ''
    // 文章数据数组
    @State articleContentList: ArticleContentData[] = []
    // 学习打卡总记录数
    @State total: number = 0
    // 当前页
    @State page: number = 1
    // 每一页大小
    @State pageSize: number = 7
    // 控制当前页面展示
    @State isShow: boolean = false
    // 定义一个状态属性,用来和Refresh组件进行双向数据绑定
    @State isRefreshing: boolean = false
    // 节流, false表示未请求, true表示正在请求
    isLoad: boolean = false
    // 是否展示文本,列表到底
    @State textShow: boolean = false

    /**
   * 生命周期函数
   */
    async aboutToAppear() {
      // 默认获取首页最新文章内容
      this.getArticleNewDataList(true, false)
    }

    /**
   * 分页查询最新文章数据
   */
    async getArticleNewDataList(isFlushed: boolean, isUpdate: boolean) {
      // 分页查询最新文章数据
      const articleDataList =
        await articleContentApi.getNewArticle({ page: this.page, pageSize: this.pageSize, title: this.title })
      isFlushed ? this.articleContentList = articleDataList.records :
        this.articleContentList.push(...articleDataList.records)
      this.total = articleDataList.total
      // 判断总数据
      if (this.total > this.page * this.pageSize) {
        this.textShow = false
      } else {
        this.textShow = true
      }
      // 页面展示
      this.isShow = true
      // 节流,防止用户重复下拉
      this.isLoad = false
      this.isRefreshing = false
      // 是否刷新
      if (isUpdate) {
        showToast("已更新")
      }
    }

    /**
   * 分页查询最热门文章数据
   */
    async getArticleHotDataList(isFlushed: boolean, isUpdate: boolean) {
      // 分页查询最热门文章数据
      const articleDataList =
        await articleContentApi.getHotArticle({ page: this.page, pageSize: this.pageSize, title: this.title })
      isFlushed ? this.articleContentList = articleDataList.records :
        this.articleContentList.push(...articleDataList.records)
      this.total = articleDataList.total
      // 判断总数据
      if (this.total > this.page * this.pageSize) {
        this.textShow = false
      } else {
        this.textShow = true
      }
      // 页面展示
      this.isShow = true
      // 节流,防止用户重复下拉
      this.isLoad = false
      this.isRefreshing = false
      // 是否刷新
      if (isUpdate) {
        showToast("已更新")
      }
    }

    /**
   * 分页查询文章数据
   */
    async getArticleDataList(isFlushed: boolean) {
      // 分页查询文章数据
      if (this.newDividerShow) {
        this.getArticleNewDataList(isFlushed, true)
      }
      if (this.hotDividerShow) {
        this.getArticleHotDataList(isFlushed, true)
      }
    }

    build() {
      Column() {
      // 标题栏
      Row({ space: 10 }) {
        Column() {
          Text($r('app.string.article_best_new'))
            .textStyles()
          // 选中标题会出现下划线
          if (this.newDividerShow) {
            Divider().dividerStyles()
          }
        }
        .onClick(() => {
          // 展示下划线
          this.hotDividerShow = false
          this.newDividerShow = true
          // 查询最新文章数据
          this.isShow = false
          this.page = 1
          this.getArticleNewDataList(true, true)
        })

        Column() {
          Text($r('app.string.article_best_hot'))
            .textStyles()
          // 选中标题会出现下划线
          if (this.hotDividerShow) {
            Divider().dividerStyles()
          }
        }
        .onClick(() => {
          this.newDividerShow = false
          this.hotDividerShow = true
          // 查询最热门文章数据
          this.isShow = false
          this.page = 1
          this.getArticleHotDataList(true, true)
        })

      }.justifyContent(FlexAlign.Start)
      .margin({ top: 10, bottom: 20 })
      .width(CommonConstant.WIDTH_FULL)

      // 搜索
      Search({ placeholder: '请输入关键字', value: $$this.keyword })
        .placeholderFont({ size: 14 })
        .textFont({ size: 14 })
        .onSubmit(() => {
          // 处理标题
          this.title = encodeURIComponent(this.keyword)
          // 分页查询文章内容
          this.isShow = false
          this.page = 1
          this.getArticleDataList(true)
        })
        .width(CommonConstant.WIDTH_FULL)
        .height(30)

      if (this.isShow) {
        Refresh({ refreshing: $$this.isRefreshing }) {
          // 内容组件
          List() {
            ForEach(this.articleContentList, (item: ArticleContentData) => {
              ListItem() {
                ArticleComponent({ articleContentData: item })
                  .onClick(() => {
                    // 路由到内容详情页
                    router.pushUrl({
                      url: RouterConstant.VIEWS_HOME_ARTICLE_INFO, params: {
                        "articleId": item.id
                      }
                    })
                  })
              }
            })
            if (this.textShow) {
              ListItem() {
                Text($r('app.string.no_have_article'))
                  .fontColor($r('app.color.common_gray'))
                  .fontSize($r('app.float.common_font_size_small'))
                  .width(CommonConstant.WIDTH_FULL)
                  .textAlign(TextAlign.Center)
                  .margin({ top: 80 })
              }
            }
          }
          .width(CommonConstant.WIDTH_FULL)
          .height(CommonConstant.HEIGHT_FULL)
          .scrollBar(BarState.Off)
          .onReachEnd(() => {
            if (!this.isLoad) {
              if (this.total > this.page * this.pageSize) {
                this.isLoad = true
                this.page++
                if (this.newDividerShow) {
                  // 分页查询最新文章数据
                  this.getArticleNewDataList(false, false)
                }
                if (this.hotDividerShow) {
                  // 分页查询最热门文章数据
                  this.getArticleHotDataList(false, false)
                }
              } else {
                this.textShow = true
              }

            }
          })
        }
        .onRefreshing(() => {
          if (!this.isLoad) {
            this.isLoad = true
            this.textShow = false
            // 页面恢复到1
            this.page = 1
            if (this.newDividerShow) {
              // 分页查询最新文章数据
              this.getArticleNewDataList(true, true)
            }
            if (this.hotDividerShow) {
              // 分页查询最热门文章数据
              this.getArticleHotDataList(true, true)
            }

          }
        })
      } else {
        // 加载组件
        LoadingComponent()
      }

    }.padding($r('app.float.common_padding'))
    .height(CommonConstant.HEIGHT_FULL)
    .width(CommonConstant.WIDTH_FULL)

  }
}

@Extend(Text)
function textStyles() {
  .fontSize($r('app.float.common_font_size_huge'))
  .fontWeight(FontWeight.Medium)
}

@Extend(Divider)
function dividerStyles() {
  .color(Color.Black)
  .width($r('app.float.common_width_tiny'))
  .strokeWidth(3)
}

5、数据渲染,接口封装

查询最新文章数据,这个函数里面有个 articleContentApi.getNewArticle方法,这个方法是作者封装的接口

/**
 * 分页查询最新文章数据
 */
async getArticleNewDataList(isFlushed: boolean, isUpdate: boolean) {
  // 分页查询最新文章数据
  const articleDataList =
    await articleContentApi.getNewArticle({ page: this.page, pageSize: this.pageSize, title: this.title })
  isFlushed ? this.articleContentList = articleDataList.records :
    this.articleContentList.push(...articleDataList.records)
  this.total = articleDataList.total
  // 判断总数据
  if (this.total > this.page * this.pageSize) {
    this.textShow = false
  } else {
    this.textShow = true
  }
  // 页面展示
  this.isShow = true
  // 节流,防止用户重复下拉
  this.isLoad = false
  this.isRefreshing = false
  // 是否刷新
  if (isUpdate) {
    showToast("已更新")
  }
}

大家可以根据东林提供的接口文档,将我们的调用接口封装成方法

这是涉及到文章的所有接口

import http from '../request/Request'
import {
  ArticleContentData,
  ArticleContentHotPageParam,
  ArticleContentNewPageParam,
  ArticleContentPageParam,
  PageVo
} from './ArticleContentApi.type'

/**
 * 文章接口
 */
class ArticleContentApi {
  /**
   * 分页查询文章内容
   */
  pageListArticleContent = (data: ArticleContentPageParam): Promise<PageVo<ArticleContentData>> => {
    return http.get('/v1/article/page?page=' + data.page + '&&pageSize=' + data.pageSize + '&&title=' + data.title +
                    '&&contentCategory=' + data.contentCategory + '&&platformCategory=' + data.platformCategory +
                    '&&difficultyCategory=' + data.difficultyCategory)
  }
  /**
   * 根据文章id查询文章详情
   */
  getArticleContentInfo = (data: number): Promise<ArticleContentData> => {
    return http.get('/v1/article/info?id=' + data)
  }
  /**
   *  用户点赞/取消点赞文章
   */
  likeArticleContent = (data: number) => {
    return http.put('/v1/article/like?id=' + data)
  }
  /**
   *  用户收藏/取消收藏文章
   */
  collectArticleContent = (data: number) => {
    return http.put('/v1/article/collect?id=' + data)
  }
  /**
   * 查看我的点赞,最近100条
   */
  getUserLike = (): Promise<Array<ArticleContentData>> => {
    return http.get('/v1/article/myLike')
  }
  /**
   *查看我的收藏,最近100条
   */
  getUserCollect = (): Promise<Array<ArticleContentData>> => {
    return http.get('/v1/article/myCollect')
  }
  /**
   *分页查看最新文章
   */
  getNewArticle = (data: ArticleContentNewPageParam): Promise<PageVo<ArticleContentData>> => {
    return http.get('/v1/article/new?page=' + data.page + '&&pageSize=' + data.pageSize + '&&title=' + data.title)
  }
  /**
   *分页查看最热文章
   */
  getHotArticle = (data: ArticleContentHotPageParam): Promise<PageVo<ArticleContentData>> => {
    return http.get('/v1/article/hot?page=' + data.page + '&&pageSize=' + data.pageSize + '&&title=' + data.title)
  }
}

const articleContentApi = new ArticleContentApi();

export default articleContentApi as ArticleContentApi;
/**
 * 时间
 */
export interface BaseTime {
  /**
   * 创建时间
   */
  createTime?: Date
  /**
   * 更新时间
   */
  updateTime?: Date
}

/**
 * 分页参数
 */
export interface PageParam {
  /**
   * 当前页
   */
  page?: number
  /**
   * 每一页展示的数据条数
   */
  pageSize?: number
}


/**
 * 分页响应参数
 */
export interface PageVo<T> {
  current: number,
  size: number,
  total: number,
  records: Array<T>
}

/**
 * 分页查询文章内容
 */
export interface ArticleContentPageParam extends PageParam {
  /**
   * 标题
   */
  title?: string
  /**
   * 内容分类:1鸿蒙 2 Java 3 web 4 运维
   */
  contentCategory?: string
  /**
   * 平台分类:1学习平台 2面试题
   */
  platformCategory?: string
  /**
   * 难度分类:1 简单 2 中等 3 困难
   */
  difficultyCategory?: string
}

/**
 * 分页查询最新文章内容入参
 */
export interface ArticleContentNewPageParam extends PageParam {
  /**
   * 标题
   */
  title: string

}

/**
 * 分页查询最热文章内容入参
 */
export interface ArticleContentHotPageParam extends PageParam {
  /**
   * 标题
   */
  title: string

}

/**
 * 文章内容数据
 */
export interface ArticleContentData extends BaseTime {
  /**
   * 文章id
   */
  id: number
  /**
   * 用户头像
   */
  avatarUri: string
  /**
   * 用户昵称
   */
  nickname: string
  /**
   * 文章标题
   */
  title: string
  /**
   * 文章内容
   */
  content: string
  /**
   * 阅读数
   */
  readCount: number
  /**
   * 点赞数
   */
  likeCount: number
  /**
   * 收藏数
   */
  collectCount: number
  /**
   * 封面url
   */
  coverUrl: string
  /**
   * 内容分类:1鸿蒙 2 Java 3 web 4 运维
   */
  contentCategory: string
  /**
   * 平台分类:1学习平台 2面试题
   */
  platformCategory: string
  /**
   * 难度分类:1 简单 2 中等 3 困难
   */
  difficultyCategory: string
  /**
   * 用户是否点赞
   */
  isLike: boolean
  /**
   * 用户是否收藏
   */
  isCollect: boolean
  /**
   * 创建时间字符串格式
   */
  time: string
}

文章详情界面布局

当我们点击文章的话会跳转到文章详情页面,携带当前点击的文章id

这边路由我们使用的是router.push()

1、新建文章详情页面

在ets/views/Home下面新建ArticleInfo.ets文件

2、设计图

3、需求分析

整个文章详情页面呈现的是使用Column布局的,里面分为多行,其他最上面标题我们可以使用Navigation组件做,鸿蒙官方推荐的,下面的文章内容按理说是渲染的html格式(富文本)的,所以我们使用RichText组件,还有点赞和收藏按理说有两种状态(未点赞、点赞,未收藏、收藏),整体文章数据也是根据路由跳转传过来的文章id进行查询的文章数据。

4、封装富文本组件

在components目录下面新建LearnRichText.ets文件

@Component
  export struct LearnRichText {
    // 富文本
    @Prop richTextContent: string = ""

    build() {
      Scroll() {
        RichText(`
                      <html>
                        <body>
                          <div style="font-size:54px">${this.richTextContent}</div>
                        <body>
                      </html>
                      `)
      }
      .layoutWeight(1)

    }
  }

5、整体代码
import { ArticleContentData } from '../../api/ArticleContentApi.type';
import { ArticleExtraInfoComponent } from '../../components/ArticleExtraInfoComponent'
import { LearnRichText } from '../../components/LearnRichText';
import { LoadingComponent } from '../../components/LoadingComponent';
import { CommonConstant } from '../../contants/CommonConstant'
import { router } from '@kit.ArkUI';
import articleContentApi from '../../api/ArticleContentApi';
import { showToast } from '../../utils/Toast';

@Entry
  @Component
  struct ArticleInfo {
    // 文章数据数组
    @Prop articleInfo: ArticleContentData
    // 控制当前页面展示
    @State isShow: boolean = false
    // 文章id
    @State articleId: number = 1
    // 节流,防止用户重复点击
    isLoad: boolean = false

    /**
    * 生命周期函数
    */
    async aboutToAppear() {
      // 获取路由传递的文章id
      const param = router.getParams() as object
      if (param) {
        this.articleId = param["articleId"] as number
        // 根据文章id查询文章数据
        this.articleInfo = await articleContentApi.getArticleContentInfo(this.articleId)
        // 展示数据
        this.isShow = true
      }
    }

    /**
   * 点赞
   * @param id
   */
    async likeArticle(id: number) {
      // 点赞或者取消点赞
      if (this.articleInfo.isLike) {
        // 取消点赞
        await articleContentApi.likeArticleContent(id)
        this.articleInfo.isLike = false
        this.articleInfo.likeCount--
        this.isLoad = false
        showToast('取消点赞成功')
      } else {
        // 点赞
        await articleContentApi.likeArticleContent(id)
        this.articleInfo.isLike = true
        this.articleInfo.likeCount++
        this.isLoad = false
        showToast('点赞成功')
      }
    }

    /**
   * 收藏
   * @param id
   */
    async collectArticle(id: number) {
      // 收藏或者取消收藏
      if (this.articleInfo.isCollect) {
        // 取消收藏
        await articleContentApi.collectArticleContent(id)
        this.articleInfo.isCollect = false
        this.articleInfo.collectCount--
        this.isLoad = false
        showToast('取消收藏成功')
      } else {
        // 收藏
        await articleContentApi.collectArticleContent(id)
        this.articleInfo.isCollect = true
        this.articleInfo.collectCount++
        this.isLoad = false
        showToast('收藏成功')
      }
    }

    build() {
      Navigation() {
        if (this.isShow) {
          Column({ space: 15 }) {
            Flex() {
              Text(this.articleInfo.title)
                .fontSize($r('app.float.common_font_size_medium'))
                .maxLines(3)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
                .fontColor(Color.Black)
                .fontWeight(FontWeight.Medium)
            }

            Row({ space: 10 }) {
              // 头像
              Image(this.articleInfo.avatarUri)
                .width(30)
                .aspectRatio(1)
                .borderRadius(20)
              // 昵称
              Text(this.articleInfo.nickname)
                .fontSize($r('app.float.common_font_size_medium'))
                .width('80%')
                .maxLines(1)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
            }.width(CommonConstant.WIDTH_FULL)

          // 展示阅读、点赞、收藏数
          Row() {
            // 文章额外信息组件
            ArticleExtraInfoComponent({ articleInfo: this.articleInfo })
            Row({ space: 15 }) {
              // 点赞
              Image(this.articleInfo.isLike ? $r('app.media.icon_like_selected') : $r('app.media.icon_like_default'))
                .width(15)
                .onClick(() => {
                  // 点赞
                  if (!this.isLoad) {
                    this.isLoad = true
                    this.likeArticle(this.articleInfo.id)
                  }
                })
              // 收藏
              Image(this.articleInfo.isCollect ? $r('app.media.icon_collect_selected') :
              $r('app.media.icon_collect_default')).width(15)
                .onClick(() => {
                  // 收藏
                  if (!this.isLoad) {
                    this.isLoad = true
                    this.collectArticle(this.articleInfo.id)
                  }
                })
            }
          }.width(CommonConstant.WIDTH_FULL)
          .justifyContent(FlexAlign.SpaceBetween)

        }.padding($r('app.float.common_padding'))

        // 分割线
        Divider()
          .strokeWidth((4))
          .color('#e6f2fe')
        // 内容正文
        LearnRichText({ richTextContent: this.articleInfo.content })
          .padding($r('app.float.common_padding'))
      } else {
        // 加载组件
        LoadingComponent()
      }
    }
    .height(CommonConstant.HEIGHT_FULL)
    .width(CommonConstant.WIDTH_FULL)
    .title($r('app.string.article_info_title'))
    .titleMode(NavigationTitleMode.Mini)
    .mode(NavigationMode.Stack)
    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])

  }
}
6、注意事项

当前系统除了登录、分页查询文章等部分接口,其他接口功能都是需要登录之后才可以使用的,这就是系统的权限控制,当前查询文章详情、收藏、点赞功能是需要登录得,未登录状态下使用是报错的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2318586.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux上位机开发实战(qt编译之谜)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多同学都喜欢用IDE&#xff0c;也能理解。因为不管是visual studio qt插件&#xff0c;还是qt creator其实都帮我们做了很多额外的工作。这里面最…

【人工智能】【Python】在Scikit-Learn中使用网格搜索对决策树调参

这次实践课最大收获非网格搜索莫属。 # 导入包 import matplotlib.pyplot as plt import numpy as np from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split, GridSearchCV # 网格搜索 from sklearn.tree import DecisionTreeClassi…

用Python代码生成批量下单json

需求 根据以下json体&#xff0c;生成230OrderList对象生成10位有序的数字字母随机数赋值给OrderDetailList.ApiOrderId 和 OrderDetailList.Traceid生成的Json文件 保存在项目JSON目录中 {"UAccount": "xxxx","Password": "","…

TCP、UDP协议的应用、ServerSocket和Socket、DatagramSocket和DatagramPacket

DAY13.1 Java核心基础 TCP协议 TCP 协议是面向连接的运算层协议&#xff0c;比较复杂&#xff0c;应用程序在使用TCP协议之前必须建立连接&#xff0c;才能传输数据&#xff0c;数据传输完毕之后需要释放连接 就好比现实生活中的打电话&#xff0c;首先确保电话打通了才能进…

配置VMware Workstation中Ubuntu虚拟机与Windows主机的剪贴板共享功能

步骤1&#xff1a;安装或更新VMware Tools组件‌ ‌卸载旧版本工具&#xff08;可选&#xff09;‌ 若已安装旧版工具&#xff0c;建议先卸载&#xff1a; sudo apt-get autoremove open-vm-tools‌安装必需组件‌ sudo apt-get updatesudo apt-get install open-vm-tools o…

深入理解Python闭包与递归:原理、应用与实践

目录 闭包 什么是闭包&#xff1a; 闭包的基本结构&#xff1a; 实现闭包的条件&#xff1a; 1.嵌套函数 2.内函数引用外部函数的变量 3.外部函数返回内部函数 4.外部函数已经执行完毕 递归函数 什么是递归函数&#xff1a; 递归函数条件 1.必须有个明确的结束条…

SeaCMS代码审计

漏洞描述 漏洞分析 根据漏洞描述定位漏洞代码 当actionsaveCus或者save时&#xff0c;可以进行一个文件写入&#xff0c;不过文件类型被进行了限制&#xff0c;只有html,htm,js,txt,css 虽然这里并不能写入php文件&#xff0c;但是当actionadd或者custom时&#xff0c;这里进行…

好看的网络安全登录页面 vue http网络安全

一、http协议 http协议是一种网络传输协议&#xff0c;规定了浏览器和服务器之间的通信方式。位于网络模型中的应用层。&#xff08;盗图小灰。ヾ(◍∇◍)&#xff89;&#xff9e;&#xff09; 但是&#xff0c;它的信息传输全部是以明文方式&#xff0c;不够安全&#xff0c;…

Unity--GPT-SoVITS接入、处理GPTAPI的SSE响应流

GPT-SoVITS GPT-SoVITS- v2&#xff08;v3也可以&#xff0c;两者对模型文件具有兼容&#xff09; 点击后 会进入新的游览器网页 ----- 看了一圈&#xff0c;发现主要问题集中在模型的训练很需要CPU&#xff0c;也就是模型的制作上&#xff0c;问题很多&#xff0c;如果有现有…

Redis哈希槽机制的实现

Redis哈希槽机制的实现 Redis集群使用哈希槽&#xff08;Hash Slot&#xff09;来管理数据分布&#xff0c;整个集群被划分为固定的16384个哈希槽。当我们在集群中存储一个键时&#xff0c;Redis会先对键进行哈希运算&#xff0c;得到一个哈希值。然后&#xff0c;Redis将该哈…

docker pull 提示timeout

通过命令行拉取对应的mysql版本提示网络超时。 开始排查&#xff0c;首先确认是否能浏览器访问。ok的&#xff0c;可以正常访问。 终端curl 排查嗯 有问题 改了下 终端 vim ~/.zshrc 加入 export HTTP_PROXY"http://127.0.0.1:7890" export HTTPS_PROXY"…

(超详细) ETL工具之Kettle

Kettle简介 kettle最早是一个开源的ETL工具&#xff0c;后命名为Pentaho Data Integration。由JAVA开发&#xff0c;支持跨平台运行&#xff0c;其特性包括&#xff1a;支持100%无编码、拖拽方式开发ETL数据管道&#xff0c;可对接包括传统数据库、文件、大数据平台、接口、流…

random_masking 函数测试

文章目录 1. description2. excel3. pytorch code 1. description 功能&#xff1a;按一定比例的随机部分样本&#xff0c;简单来说就是按照一定的比例将行向量从小到大的顺序提取出来。思考1&#xff1a; 用了均匀分布&#xff0c;并且按照一定比例&#xff0c;取前prob概率来…

TDengine 中的流式计算

简介 TDengine 中的流计算&#xff0c;功能相当于简化版的 FLINK &#xff0c; 具有实时计算&#xff0c;计算结果可以输出到超级表中存储&#xff0c;同时也可用于窗口预计算&#xff0c;加快查询速度。 创建流式计算 CREATE STREAM [IF NOT EXISTS] stream_name [stream_o…

Java 大视界 -- Java 大数据在智慧交通自动驾驶仿真与测试数据处理中的应用(136)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

JVM的一些知识

JVM简介 JVM 是 Java Virtual Machine 的简称&#xff0c;意为 Java 虚拟机。 虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。常见的虚拟机&#xff1a;JVM、VMwave、Virtual Box。 JVM 和其他两个虚拟机的区别&#xff1a; VMw…

C语言每日一练——day_7

引言 针对初学者&#xff0c;每日练习几个题&#xff0c;快速上手C语言。第七天。&#xff08;连续更新中&#xff09; 采用在线OJ的形式 什么是在线OJ&#xff1f; 在线判题系统&#xff08;英语&#xff1a;Online Judge&#xff0c;缩写OJ&#xff09;是一种在编程竞赛中用…

Java使用FFmpegFrameGrabber进行视频拆帧,结合Thumbnails压缩图片保存到文件夹

引入依赖 <dependency><groupId>net.coobird</groupId><artifactId>thumbnailator</artifactId><version>0.4.17</version></dependency><dependency><groupId>org.bytedeco</groupId><artifactId>ja…

用hexo初始化博客执行hexo init时碰到的问题

用hexo初始化博客执行hexo init时碰到的问题 $ hexo init myblog INFO Cloning hexo-starter https://github.com/hexojs/hexo-starter.git fatal: unable to access https://github.com/hexojs/hexo-starter.git/: SSL certificate problem: unable to get local issuer cer…

4.1--入门知识扫盲,ISO知识体系介绍(看一遍,协议啥的全部记住)

OSI七层模型&#xff1a;网络世界的"七重天"生存指南&#xff08;附快递小哥版图解&#xff09; “如果你觉得网络分层很抽象&#xff0c;那就想象自己在寄快递” —— 来自一个被三次握手逼疯的程序员 开场白&#xff1a;网络通信就像送外卖 假设你要给隔壁妹子送奶…