新聞數據加載(鴻蒙App開發實戰)

本案例基于ArkTS的聲明式開發范式,介紹了數據請求和onTouch事件的使用。包含以下功能:

  1. 數據請求。
  2. 列表下拉刷新。
  3. 列表上拉加載。

網絡數據請求需要權限:ohos.permission.INTERNET

一、案例效果截圖

操作說明:

  • 點擊應用進入主頁面,頁面使用tabBar展示新聞分類,tabContent展示新聞列表,新聞分類和新聞列表通過請求nodejs服務端獲取。
  • 點擊頁簽或左右滑動頁面,切換標簽并展示對應新聞類型的數據。
  • 新聞列表頁面,滑動到新聞列表首項數據,接著往下滑動會觸發下拉刷新操作,頁面更新初始4條新聞數據,滑動到新聞列表最后一項數據,往上拉會觸發上拉加載操作,新聞列表會在后面加載4條新聞數據。

二、案例運用到的知識點

  1. 核心知識點
  • List組件:列表包含一系列相同寬度的列表項。
  • Tabs:通過頁簽進行內容視圖切換。
  • TabContent:僅在Tabs中使用,對應一個切換頁簽的內容視圖。
  • 數據請求:提供HTTP數據請求能力。
  • 觸摸事件onTouch:手指觸摸動作觸發該回調。
  1. 其他知識點
  • ArkTS 語言基礎
  • V2版狀態管理:@ComponentV2/@Local/@Provider/@Consumer
  • 渲染控制:if/ForEach
  • 自定義組件和組件生命周期
  • 自定義構建函數@Builder
  • @Extend:定義擴展樣式
  • Navigation:導航組件
  • 內置組件:Stack/Progress/Image/Column/Row/Text/Button
  • 常量與資源分類的訪問
  • MVVM模式

三、代碼結構

├──entry/src/main/ets                   // ArkTS代碼區
│  ├──common
│  │  ├──constant
│  │  │  └──CommonConstant.ets          // 公共常量類
│  │  └──utils
│  │     ├──HttpUtil.ets                // 網絡請求方法
│  │     ├──Logger.ets                  // 日志打印工具
│  │     ├──PullDownRefresh.ets         // 下拉刷新方法
│  │     └──PullUpLoadMore.ets          // 上拉加載更多方法
│  ├──entryability
│  │  └──EntryAbility.ets               // 程序入口類
│  ├──pages
│  │  └──Index.ets                      // 入口文件
│  ├──view
│  │  ├──CustomRefreshLoadLayout.ets    // 下拉刷新、上拉加載布局文件
│  │  ├──LoadMoreLayout.ets             // 上拉加載布局封裝
│  │  ├──NewsItem.ets                   // 新聞數據
│  │  ├──NewsList.ets                   // 新聞列表
│  │  ├──NoMoreLayout.ets               // 沒有更多數據封裝
│  │  ├──RefreshLayout.ets              // 下拉刷新布局封裝
│  │  └──TabBar.ets                     // 新聞類型頁簽
│  └──viewmodel
│     ├──NewsData.ets                   // 新聞數據實體類
│     ├──NewsModel.ets                  // 新聞數據模塊信息
│     ├──NewsTypeModel.ets              // 新聞類型實體類
│     ├──NewsViewModel.ets              // 新聞數據獲取模塊
│     └──ResponseResult.ets             // 請求結果實體類
└──entry/src/main/resources             // 資源文件目錄

四、公共文件與資源

本案例涉及到的常量類和工具類代碼如下:

  1. 通用常量類
// entry/src/main/ets/common/constants/CommonConstants.ets
import NewsTypeModel from '../../viewmodel/NewsTypeModel'// 服務器的主機地址
export class CommonConstant {static readonly SERVER: string = 'http://192.168.31.150:3000'// 獲取新聞類型static readonly GET_NEWS_TYPE: string = 'news/getNewsType'// 獲取新聞列表static readonly GET_NEWS_LIST: string = 'news/getNewsList'// 請求成功的狀態碼static readonly SERVER_CODE_SUCCESS: string = 'success'// 偏移系數static readonly Y_OFF_SET_COEFFICIENT: number = 0.1// 頁面大小static readonly PAGE_SIZE: number = 4// 刷新和加載的高度static readonly CUSTOM_LAYOUT_HEIGHT: number = 70// HTTP 請求成功狀態碼static readonly HTTP_CODE_200: number = 200// 動畫延遲時間static readonly DELAY_ANIMATION_DURATION: number = 300// 延遲時間static readonly DELAY_TIME: number = 1000// 動畫持續時間static readonly ANIMATION_DURATION: number = 2000// HTTP 超時時間static readonly HTTP_READ_TIMEOUT: number = 10000// 寬度占滿static readonly FULL_WIDTH: string = '100%'// 高度占滿static readonly FULL_HEIGHT: string = '100%'// TabBars 相關常量static readonly TabBars_UN_SELECT_TEXT_FONT_SIZE: number = 18static readonly TabBars_SELECT_TEXT_FONT_SIZE: number = 24static readonly TabBars_UN_SELECT_TEXT_FONT_WEIGHT: number = 400static readonly TabBars_SELECT_TEXT_FONT_WEIGHT: number = 700static readonly TabBars_BAR_HEIGHT: string = '7.2%'static readonly TabBars_HORIZONTAL_PADDING: string  = '2.2%'static readonly TabBars_BAR_WIDTH: string = '100%'static readonly TabBars_DEFAULT_NEWS_TYPES: Array<NewsTypeModel> = [{ id: 0, name: '全部' },{ id: 1, name: '國內' },{ id: 2, name: '國際' },{ id: 3, name: '娛樂' },{ id: 4, name: '軍事' },{ id: 5, name: '體育' },{ id: 6, name: '科技' },{ id: 7, name: '財經' }]// 新聞列表相關常量static readonly NewsListConstant_LIST_DIVIDER_STROKE_WIDTH: number = 0.5static readonly NewsListConstant_GET_TAB_DATA_TYPE_ONE: number = 1static readonly NewsListConstant_ITEM_BORDER_RADIUS: number = 16static readonly NewsListConstant_NONE_IMAGE_SIZE: number = 120static readonly NewsListConstant_NONE_TEXT_opacity: number = 0.6static readonly NewsListConstant_NONE_TEXT_size: number = 16static readonly NewsListConstant_NONE_TEXT_margin: number = 12static readonly NewsListConstant_ITEM_MARGIN_TOP: string = '1.5%'static readonly NewsListConstant_LIST_MARGIN_LEFT: string = '3.3%'static readonly NewsListConstant_LIST_MARGIN_RIGHT: string = '3.3%'static readonly NewsListConstant_ITEM_HEIGHT: string = '32%'static readonly NewsListConstant_LIST_WIDTH: string =  '93.3%'// 新聞標題相關常量static readonly NewsTitle_TEXT_MAX_LINES: number = 3static readonly NewsTitle_TEXT_FONT_SIZE: number = 20static readonly NewsTitle_TEXT_FONT_WEIGHT: number = 500static readonly NewsTitle_TEXT_MARGIN_LEFT: string = '2.4%'static readonly NewsTitle_TEXT_MARGIN_TOP: string = '7.2%'static readonly NewsTitle_TEXT_HEIGHT: string = '9.6%'static readonly NewsTitle_TEXT_WIDTH: string = '78.6%'static readonly NewsTitle_IMAGE_MARGIN_LEFT: string = '3.5%'static readonly NewsTitle_IMAGE_MARGIN_TOP: string = '7.9%'static readonly NewsTitle_IMAGE_HEIGHT: string = '8.9%'static readonly NewsTitle_IMAGE_WIDTH: string = '11.9%'// 新聞內容相關常量static readonly NewsContent_WIDTH: string = '93%'static readonly NewsContent_HEIGHT: string = '16.8%'static readonly NewsContent_MARGIN_LEFT: string = '3.5%'static readonly NewsContent_MARGIN_TOP: string = '3.4%'static readonly NewsContent_MAX_LINES: number = 2static readonly NewsContent_FONT_SIZE: number = 15// 新聞來源相關常量static readonly NewsSource_MAX_LINES: number = 1static readonly NewsSource_FONT_SIZE: number = 12static readonly NewsSource_MARGIN_LEFT: string = '3.5%'static readonly NewsSource_MARGIN_TOP: string = '3.4%'static readonly NewsSource_HEIGHT: string = '7.2%'static readonly NewsSource_WIDTH: string = '93%'// 新聞網格相關常量static readonly NewsGrid_MARGIN_LEFT: string = '3.5%'static readonly NewsGrid_MARGIN_RIGHT: string = '3.5%'static readonly NewsGrid_MARGIN_TOP: string = '5.1%'static readonly NewsGrid_WIDTH: string = '93%'static readonly NewsGrid_HEIGHT: string = '31.5%'static readonly NewsGrid_ASPECT_RATIO: number = 4static readonly NewsGrid_COLUMNS_GAP: number = 5static readonly NewsGrid_ROWS_TEMPLATE: string = '1fr'static readonly NewsGrid_IMAGE_BORDER_RADIUS: number = 8// 刷新布局相關常量static readonly RefreshLayout_MARGIN_LEFT: string = '40%'static readonly RefreshLayout_TEXT_MARGIN_BOTTOM: number = 1static readonly RefreshLayout_TEXT_MARGIN_LEFT: number = 7static readonly RefreshLayout_TEXT_FONT_SIZE: number = 17static readonly RefreshLayout_IMAGE_WIDTH: number = 18static readonly RefreshLayout_IMAGE_HEIGHT: number = 18// 無更多內容布局相關常量static readonly NoMoreLayoutConstant_NORMAL_PADDING: number = 8static readonly NoMoreLayoutConstant_TITLE_FONT: string = '16fp'// 刷新相關常量static readonly RefreshConstant_DELAY_PULL_DOWN_REFRESH: number = 50static readonly RefreshConstant_CLOSE_PULL_DOWN_REFRESH_TIME: number = 150static readonly RefreshConstant_DELAY_SHRINK_ANIMATION_TIME: number = 500
}// 刷新狀態枚舉
export const enum RefreshState {DropDown = 0,Release = 1,Refreshing = 2,Success = 3,Fail = 4
}// 新聞列表狀態枚舉
export const enum PageState {Loading = 0,Success = 1,Fail = 2
}// 刷新和加載類型
export const enum LoadingType {Loading = 0,Refresh = 1,LoadMore = 2,
}// 請求內容類型枚舉
export const enum ContentType {JSON = 'application/json'
}

本案例涉及到的資源文件如下:

  1. string.json
// entry/src/main/resources/base/element/string.json
{"string": [{"name": "module_desc","value": "description"},{"name": "EntryAbility_desc","value": "description"},{"name": "EntryAbility_label","value": "newsData"},{"name": "pull_up_load_text","value": "加載中..."},{"name": "pull_down_refresh_text","value": "下拉刷新"},{"name": "release_refresh_text","value": "松開刷新"},{"name": "refreshing_text","value": "正在刷新"},{"name": "refresh_success_text","value": "刷新成功"},{"name": "refresh_fail_text","value": "刷新失敗"},{"name": "http_error_message","value": "網絡請求失敗,請稍后嘗試!"},{"name": "page_none_msg","value": "網絡加載失敗"},{"name": "prompt_message","value": "沒有更多數據了"},{"name": "dependency_reason","value": "允許應用在新聞數據加載場景使用Internet網絡。"}]
}
  1. color.json
// entry/src/main/resources/base/element/color.json
{"color": [{"name": "start_window_background","value": "#FFFFFF"},{"name": "white","value": "#FFFFFF"},{"name": "color_index","value": "#1E67DC"},{"name": "fontColor_text","value": "#000000"},{"name": "fontColor_text1","value": "#8A8A8A"},{"name": "fontColor_text2","value": "#FF989898"},{"name": "fontColor_text3","value": "#182431"},{"name": "listColor","value": "#F1F3F5"},{"name": "dividerColor","value": "#E2E2E2"}]
}

其他資源請到源碼中獲取。

五、界面搭建

  1. 主頁面
// entry/src/main/ets/pages/Index.ets
import TabBar from '../view/TabBar'
import { CommonConstant as Const } from '../common/constant/CommonConstant'/*** Index 應用程序的入口點。*/
@Entry
@ComponentV2
struct Index {build() {Column() {// TabBar單獨抽離構建TabBar()}.width(Const.FULL_WIDTH).backgroundColor($r('app.color.listColor')).justifyContent(FlexAlign.Center)}
}
  1. TabBar組件
// entry/src/main/ets/view/TabBar.ets
import NewsList from '../view/NewsList'
import { CommonConstant as Const } from '../common/constant/CommonConstant'
import NewsTypeModel from '../viewmodel/NewsTypeModel'
import NewsViewModel from '../viewmodel/NewsViewModel'/*** tabBar 組件,提供新聞類別的導航功能。*/
@ComponentV2
export default struct TabBar {// 存儲新聞類別數組,默認為 NewsViewModel 提供的默認類別列表@Local tabBarArray: NewsTypeModel[] = NewsViewModel.getDefaultTypeList()// 記錄當前選中的 tab 索引@Local currentIndex: number = 0// 記錄當前的頁面編號(用于分頁)@Local currentPage: number = 1/*** 構建單個 Tab 組件的 UI。* @param {number} index - tab 的索引*/@Builder TabBuilder(index: number) {Column() {Text(this.tabBarArray[index].name).height(Const.FULL_HEIGHT).padding({left: Const.TabBars_HORIZONTAL_PADDING, right: Const.TabBars_HORIZONTAL_PADDING}).fontSize(this.currentIndex === index ? Const.TabBars_SELECT_TEXT_FONT_SIZE : Const.TabBars_UN_SELECT_TEXT_FONT_SIZE) // 選中時的字體大小.fontWeight(this.currentIndex === index ? Const.TabBars_SELECT_TEXT_FONT_WEIGHT : Const.TabBars_UN_SELECT_TEXT_FONT_WEIGHT) // 選中時的字體粗細.fontColor($r('app.color.fontColor_text3'))}}/*** 組件即將出現時觸發,獲取新聞類別列表。*/aboutToAppear() {NewsViewModel.getNewsTypeList().then((typeList: NewsTypeModel[]) => {this.tabBarArray = typeList // 成功獲取數據后更新 tabBarArray}).catch((typeList: NewsTypeModel[]) => {this.tabBarArray = typeList // 失敗時也使用返回的列表})}/*** 構建 Tab 組件的 UI 結構。*/build() {Tabs() {// 遍歷 tabBarArray 數組,創建對應的 TabContentForEach(this.tabBarArray, (tabsItem: NewsTypeModel) => {TabContent() {Column() {NewsList({ currentIndex: this.currentIndex }) // 顯示新聞列表}}.tabBar(this.TabBuilder(tabsItem.id)) // 使用 TabBuilder 構建 tabBar}, (item: NewsTypeModel) => JSON.stringify(item))}.barHeight(Const.TabBars_BAR_HEIGHT).barMode(BarMode.Scrollable) // 設置 TabBar 為可滾動模式.barWidth(Const.TabBars_BAR_WIDTH) // 設置 TabBar 寬度.onChange((index: number) => {this.currentIndex = index // 更新當前選中的 tab 索引this.currentPage = 1 // 重置當前頁碼}).vertical(false) // 設定 TabBar 水平排列}
}

關鍵代碼說明:

  • NewsViewModel.getNewsTypeList(),獲取TabBar名字的數組。
  • NewsList({currentIndex: this.currentIndex}),每個Tab的頁面內容均由NewsList顯示。組件接收當前Tab的索引。這個組件將在下一節進行詳細解讀。
  1. 新聞TabBar內容模型
// entry/src/main/ets/viewmodel/NewsTypeModel.ets
export default class NewsTypeModel {id: number = 0name: ResourceStr = ''
}
  1. 新聞TabBar內容加載
// entry/src/main/ets/viewmodel/NewsViewModel.ets
import { CommonConstant as Const } from '../common/constant/CommonConstant'
import { NewsData } from './NewsData'
import NewsTypeModel from './NewsTypeModel'
import { httpRequestGet } from '../common/utils/HttpUtil'
import Logger from '../common/utils/Logger'
import ResponseResult from './ResponseResult'class NewsViewModel {/*** 從服務器獲取新聞類型列表。** @return 新聞類型列表(NewsTypeBean[])。*/getNewsTypeList(): Promise<NewsTypeModel[]> {return new Promise((resolve: Function, reject: Function) => {let url = `${Const.SERVER}/${Const.GET_NEWS_TYPE}`httpRequestGet(url).then((data: ResponseResult) => {if (data.code === Const.SERVER_CODE_SUCCESS) {resolve(data.data)} else {reject(Const.TabBars_DEFAULT_NEWS_TYPES)}}).catch(() => {reject(Const.TabBars_DEFAULT_NEWS_TYPES)})})}/*** 獲取默認的新聞類型列表。** @return 新聞類型列表(NewsTypeBean[])。*/getDefaultTypeList(): NewsTypeModel[] {return Const.TabBars_DEFAULT_NEWS_TYPES}
}let newsViewModel = new NewsViewModel()export default newsViewModel as NewsViewModel

關鍵代碼說明:

  • getNewsTypeList(): Promise<NewsTypeModel[]>,方法返回Promise。Promise成功或失敗都返回NewsTypeModel類型的數組,成功返回后端接口數據,失敗返回默認值(Const.TabBars_DEFAULT_NEWS_TYPES)。
  • httpRequestGet(url),獲取數據的方法,返回Promise。
  1. HTTP數據請求工具類
// entry/src/main/ets/common/utils/HttpUtils.ets
import { http } from '@kit.NetworkKit'
import ResponseResult from '../../viewmodel/ResponseResult'
import { CommonConstant as Const, ContentType } from '../constant/CommonConstant'/*** 向指定 URL 發起 HTTP GET 請求。* @param url 請求的 URL。* @returns Promise<ResponseResult> 返回服務器響應數據。*/
export function httpRequestGet(url: string): Promise<ResponseResult> {let httpRequest = http.createHttp() // 創建 HTTP 請求實例// 發起 GET 請求let responseResult = httpRequest.request(url, {method: http.RequestMethod.GET, // 請求方法為 GETreadTimeout: Const.HTTP_READ_TIMEOUT, // 讀取超時時間header: {'Content-Type': ContentType.JSON // 設置請求頭,指定內容類型為 JSON},connectTimeout: Const.HTTP_READ_TIMEOUT, // 連接超時時間extraData: {} // 額外數據,當前未使用})let serverData: ResponseResult = new ResponseResult() // 創建返回結果對象// 處理服務器響應數據return responseResult.then((value: http.HttpResponse) => {if (value.responseCode === Const.HTTP_CODE_200) { // 判斷 HTTP 狀態碼是否為 200let result = `${value.result}` // 獲取返回的數據let resultJson: ResponseResult = JSON.parse(result) // 解析 JSON 數據// 判斷服務器返回的業務狀態碼if (resultJson.code === Const.SERVER_CODE_SUCCESS) { serverData.data = resultJson.data // 設置數據字段}serverData.code = resultJson.code // 設置狀態碼serverData.msg = resultJson.msg // 設置返回消息} else {serverData.msg  // 處理 HTTP 錯誤信息= `${$r('app.string.http_error_message')}&${value.responseCode}`}return serverData // 返回處理后的數據}).catch(() => {serverData.msg = $r('app.string.http_error_message') // 處理請求異常return serverData // 返回錯誤信息})
}
  1. 網絡請求返回的數據結構
/** 網絡請求返回的數據。 */
export default class ResponseResult {/** 網絡請求返回的狀態碼:成功、失敗。 */code: string/** 網絡請求返回的消息。 */msg: string | Resource/** 網絡請求返回的數據。 */data: string | Object | ArrayBufferconstructor() {this.code = ''this.msg = ''this.data = ''}
}

六、列表數據請求

  1. 新聞列表組件
// entry/src/main/ets/view/NewsList.ets
import { promptAction } from '@kit.ArkUI'
import {CommonConstant as Const,PageState
} from '../common/constant/CommonConstant'
import NewsItem from './NewsItem'
import LoadMoreLayout from './LoadMoreLayout'
import RefreshLayout from './RefreshLayout'
import CustomRefreshLoadLayout from './CustomRefreshLoadLayout'
import { CustomRefreshLoadLayoutClass, NewsData } from '../viewmodel/NewsData'
import NewsViewModel from '../viewmodel/NewsViewModel'
import NoMoreLayout from './NoMoreLayout'
import NewsModel from '../viewmodel/NewsModel'/*** 新聞列表組件,用于展示新聞內容,并支持下拉刷新和上拉加載更多。*/
@ComponentV2
export default struct NewsList {// 維護新聞數據的模型@Local newsModel: NewsModel = new NewsModel()// 記錄當前選中的新聞類別索引@Param currentIndex: number = 0/*** 監聽當前選中的類別索引變化,并更新新聞列表。*/@Monitor('currentIndex')changeCategory() {this.newsModel.currentPage = 1 // 重置當前頁碼NewsViewModel.getNewsList(this.newsModel.currentPage, this.newsModel.pageSize, Const.GET_NEWS_LIST).then((data: NewsData[]) => {this.newsModel.pageState = PageState.Success // 數據加載成功if (data.length === this.newsModel.pageSize) {this.newsModel.currentPage++ // 還有更多數據時,頁碼自增this.newsModel.hasMore = true} else {this.newsModel.hasMore = false // 沒有更多數據}this.newsModel.newsData = data // 更新新聞數據}).catch((err: string | Resource) => {promptAction.showToast({message: err, // 顯示錯誤信息duration: Const.ANIMATION_DURATION})this.newsModel.pageState = PageState.Fail // 設置加載失敗狀態})}/*** 組件即將加載時,初始化新聞數據。*/aboutToAppear() {this.changeCategory()}/*** 構建新聞列表 UI。*/build() {Column() {if (this.newsModel.pageState === PageState.Success) {this.ListLayout() // 正常加載新聞列表} else if (this.newsModel.pageState === PageState.Loading) {this.LoadingLayout() // 顯示加載動畫} else {this.FailLayout() // 顯示加載失敗界面}}.width(Const.FULL_WIDTH).height(Const.FULL_HEIGHT).justifyContent(FlexAlign.Center)}/*** 顯示加載動畫的布局。*/@Builder LoadingLayout() {CustomRefreshLoadLayout({customRefreshLoadClass: new CustomRefreshLoadLayoutClass(true,$r('app.media.ic_pull_up_load'),$r('app.string.pull_up_load_text'),this.newsModel.pullDownRefreshHeight)})}/*** 新聞列表布局,包括新聞項、下拉刷新和加載更多。*/@Builder ListLayout() {List() {// 下拉刷新組件ListItem() {RefreshLayout({refreshLayoutClass: new CustomRefreshLoadLayoutClass(this.newsModel.isVisiblePullDown,this.newsModel.pullDownRefreshImage,this.newsModel.pullDownRefreshText,this.newsModel.pullDownRefreshHeight)})}// 遍歷新聞數據,渲染新聞列表項ForEach(this.newsModel.newsData, (item: NewsData) => {ListItem() {NewsItem({ newsData: item }) // 單個新聞項}.height(Const.NewsListConstant_ITEM_HEIGHT).backgroundColor($r('app.color.white')).margin({ top: Const.NewsListConstant_ITEM_MARGIN_TOP }).borderRadius(Const.NewsListConstant_ITEM_BORDER_RADIUS)}, (item: NewsData, index?: number) => JSON.stringify(item) + index)// 加載更多或顯示無更多數據ListItem() {if (this.newsModel.hasMore) {LoadMoreLayout({loadMoreLayoutClass: new CustomRefreshLoadLayoutClass(this.newsModel.isVisiblePullUpLoad,this.newsModel.pullUpLoadImage,this.newsModel.pullUpLoadText,this.newsModel.pullUpLoadHeight)})} else {NoMoreLayout()}}}.width(Const.NewsListConstant_LIST_WIDTH).height(Const.FULL_HEIGHT).margin({ left: Const.NewsListConstant_LIST_MARGIN_LEFT, right: Const.NewsListConstant_LIST_MARGIN_RIGHT }).backgroundColor($r('app.color.listColor')).divider({color: $r('app.color.dividerColor'),strokeWidth: Const.NewsListConstant_LIST_DIVIDER_STROKE_WIDTH,endMargin: Const.NewsListConstant_LIST_MARGIN_RIGHT}).edgeEffect(EdgeEffect.None) // 取消回彈效果.scrollBar(BarState.Off) // 關閉滾動條.offset({ x: 0, y: `${this.newsModel.offsetY}px` }) // 處理滾動偏移量.onScrollIndex((start: number, end: number) => {this.newsModel.startIndex = start // 監聽當前可見列表的索引范圍this.newsModel.endIndex = end})}/*** 加載失敗時的布局。*/@Builder FailLayout() {Image($r('app.media.none')).height(Const.NewsListConstant_NONE_IMAGE_SIZE).width(Const.NewsListConstant_NONE_IMAGE_SIZE)Text($r('app.string.page_none_msg')).opacity(Const.NewsListConstant_NONE_TEXT_opacity).fontSize(Const.NewsListConstant_NONE_TEXT_size).fontColor($r('app.color.fontColor_text3')).margin({ top: Const.NewsListConstant_NONE_TEXT_margin })}
}

關鍵代碼解讀:

  • 在aboutToAppear()方法里獲取新聞數據,將數據加載到新聞列表頁面ListLayout布局中。
  • 根據pageState的值是否為Success、Loading和Fail,來加載新聞列表、Loading動畫和失敗界面。
  • ListLayout構建函數渲染列表,從上至下依次調用RefreshLayout、NewsItem、LoadMoreLayout/NoMoreLayout等組件。
  • LoadingLayout組件直接渲染CustomRefreshLoadLayout組件。
  1. 列表項組件
// entry/src/main/ets/view/NewsItem.ets
import { NewsData, NewsFile } from '../viewmodel/NewsData'
import { CommonConstant as Const } from '../common/constant/CommonConstant'/*** NewsItem組件*/
@ComponentV2
export default struct NewsItem {// 從父組件接收newsData@Param newsData: NewsData = new NewsData()build() {Column() {Row() {Image($r('app.media.news')).width(Const.NewsTitle_IMAGE_WIDTH).height(Const.NewsTitle_IMAGE_HEIGHT).margin({top: Const.NewsTitle_IMAGE_MARGIN_TOP,left: Const.NewsTitle_IMAGE_MARGIN_LEFT}).objectFit(ImageFit.Fill)Text(this.newsData.title).fontSize(Const.NewsTitle_TEXT_FONT_SIZE).fontColor($r('app.color.fontColor_text')).height(Const.NewsTitle_TEXT_HEIGHT).width(Const.NewsTitle_TEXT_WIDTH).maxLines(Const.NewsTitle_TEXT_MAX_LINES).margin({ left: Const.NewsTitle_TEXT_MARGIN_LEFT, top: Const.NewsTitle_TEXT_MARGIN_TOP }).textOverflow({ overflow: TextOverflow.Ellipsis }).fontWeight(Const.NewsTitle_TEXT_FONT_WEIGHT)}Text(this.newsData.content).fontSize(Const.NewsContent_FONT_SIZE).fontColor($r('app.color.fontColor_text')).height(Const.NewsContent_HEIGHT).width(Const.NewsContent_WIDTH).maxLines(Const.NewsContent_MAX_LINES).margin({left: Const.NewsContent_MARGIN_LEFT, top: Const.NewsContent_MARGIN_TOP}).textOverflow({ overflow: TextOverflow.Ellipsis })Grid() {ForEach(this.newsData.imagesUrl, (itemImg: NewsFile) => {GridItem() {Image(Const.SERVER + itemImg.url).objectFit(ImageFit.Cover).borderRadius(Const.NewsGrid_IMAGE_BORDER_RADIUS)}}, (itemImg: NewsFile, index?: number)=>JSON.stringify(itemImg) + index)}.columnsTemplate('1fr '.repeat(this.newsData.imagesUrl.length)).columnsGap(Const.NewsGrid_COLUMNS_GAP).rowsTemplate(Const.NewsGrid_ROWS_TEMPLATE).width(Const.NewsGrid_WIDTH).height(Const.NewsGrid_HEIGHT).margin({left: Const.NewsGrid_MARGIN_LEFT, top: Const.NewsGrid_MARGIN_TOP,right: Const.NewsGrid_MARGIN_RIGHT})Text(this.newsData.source).fontSize(Const.NewsSource_FONT_SIZE).fontColor($r('app.color.fontColor_text2')).height(Const.NewsSource_HEIGHT).width(Const.NewsSource_WIDTH).maxLines(Const.NewsSource_MAX_LINES).margin({left: Const.NewsSource_MARGIN_LEFT, top: Const.NewsSource_MARGIN_TOP}).textOverflow({ overflow: TextOverflow.None })}.alignItems(HorizontalAlign.Start)}
}
  1. 下拉刷新組件
// entry/src/main/ets/view/RefreshLayout.ets
import CustomRefreshLoadLayout from './CustomRefreshLoadLayout'
import { CustomRefreshLoadLayoutClass } from '../viewmodel/NewsData'/*** RefreshLayout組件。*/
@ComponentV2
export default struct RefreshLayout {@Param refreshLayoutClass: CustomRefreshLoadLayoutClass = new CustomRefreshLoadLayoutClass(true,$r('app.media.ic_pull_up_load'),$r('app.string.pull_up_load_text'),0)build() {Column() {if (this.refreshLayoutClass.isVisible) {CustomRefreshLoadLayout({ customRefreshLoadClass: new CustomRefreshLoadLayoutClass(this.refreshLayoutClass.isVisible, this.refreshLayoutClass.imageSrc, this.refreshLayoutClass.textValue,this.refreshLayoutClass.heightValue) })}}}
}

關鍵代碼說明:

  • 由于存在this.refreshLayoutClass.isVisible(刷新組件需要顯示)的邏輯,因此單獨抽離了這個組件來過渡,最終渲染的是CustomRefreshLoadLayout組件。
  1. 上拉加載更多組件
// entry/src/main/ets/view/LoadMoarLayout.ets
import CustomRefreshLoadLayout from './CustomRefreshLoadLayout'
import { CustomRefreshLoadLayoutClass } from '../viewmodel/NewsData'/*** LoadMoreLayout組件。*/
@ComponentV2
export default struct LoadMoreLayout {@Param loadMoreLayoutClass: CustomRefreshLoadLayoutClass = new CustomRefreshLoadLayoutClass(true,$r('app.media.ic_pull_up_load'),$r('app.string.pull_up_load_text'),0)build() {Column() {if (this.loadMoreLayoutClass.isVisible) {CustomRefreshLoadLayout({customRefreshLoadClass: new CustomRefreshLoadLayoutClass(this.loadMoreLayoutClass.isVisible,this.loadMoreLayoutClass.imageSrc, this.loadMoreLayoutClass.textValue, this.loadMoreLayoutClass.heightValue)})} else {CustomRefreshLoadLayout({customRefreshLoadClass: new CustomRefreshLoadLayoutClass(this.loadMoreLayoutClass.isVisible,this.loadMoreLayoutClass.imageSrc, this.loadMoreLayoutClass.textValue, 0)})}}}
}

關鍵代碼說明:

  • 由于存在this.loadMoreLayoutClass.isVisible(加載更多組件需要顯示)的邏輯,因此單獨抽離了這個組件來過渡,最終渲染的是CustomRefreshLoadLayout組件。
  1. 新聞數據模型
// entry/src/main/ets/viewmodel/NewsData.ets
/*** 新聞列表項信息*/
@ObservedV2
export class NewsData {@Trace title: string = '' // 新聞列表項標題@Trace content: string = '' // 新聞列表項內容@Trace imagesUrl: Array<NewsFile> = [new NewsFile()] // 新聞列表項圖片地址@Trace source: string = '' // 新聞列表項來源
}/*** 新聞圖片列表項信息*/
export class NewsFile {id: number = 0 // 新聞圖片列表項 IDurl: string = '' // 新聞圖片列表項 URLtype: number = 0 // 新聞圖片列表項類型newsId: number = 0 // 新聞圖片列表項新聞 ID
}/*** 自定義刷新加載布局數據*/
@ObservedV2
export class CustomRefreshLoadLayoutClass {@Trace isVisible: boolean // 自定義刷新加載布局是否可見@Trace imageSrc: Resource // 自定義刷新加載布局圖片資源@Trace textValue: Resource // 自定義刷新加載布局文本資源@Trace heightValue: number // 自定義刷新加載布局高度值constructor(isVisible: boolean, imageSrc: Resource, textValue: Resource, heightValue: number) {this.isVisible = isVisiblethis.imageSrc = imageSrcthis.textValue = textValuethis.heightValue = heightValue}
}

關鍵代碼說明:

  • NewsData類通過@ObservedV2裝飾,類中的屬性通過@Trace裝飾,使得該類可觀察,屬性可追蹤。
  • CustomRefreshLoadLayoutClass類通過@ObservedV2裝飾,類中的屬性通過@Trace裝飾,使得該類可觀察,屬性可追蹤。
  1. 獲取新聞列表
// entry/src/main/ets/viewmodel/NewsViewModel.ets
// ...
import { CommonConstant as Const } from '../common/constant/CommonConstant'
import { NewsData } from './NewsData'
import { httpRequestGet } from '../common/utils/HttpUtil'
import Logger from '../common/utils/Logger'
import ResponseResult from './ResponseResult'class NewsViewModel {// .../*** 從服務器獲取新聞列表。** @param currentPage 當前頁碼。* @param pageSize 每頁新聞條數。* @param path 請求接口路徑。* @return 新聞數據列表(NewsData[])。*/getNewsList(currentPage: number, pageSize: number, path: string): Promise<NewsData[]> {return new Promise(async (resolve: Function, reject: Function) => {let url = `${Const.SERVER}/${path}`url += '?currentPage=' + currentPage + '&pageSize=' + pageSizehttpRequestGet(url).then((data: ResponseResult) => {if (data.code === Const.SERVER_CODE_SUCCESS) {resolve(data.data)} else {Logger.error('獲取新聞列表失敗', JSON.stringify(data))reject($r('app.string.page_none_msg'))}}).catch((err: Error) => {Logger.error('獲取新聞列表失敗', JSON.stringify(err))reject($r('app.string.http_error_message'))})})}
}let newsViewModel = new NewsViewModel()export default newsViewModel as NewsViewModel
  1. 新聞數據列表類
// entry/src/main/ets/viewmodel/NewsModel.ets
import { CommonConstant as Const, PageState
} from '../common/constant/CommonConstant'
import { NewsData } from './NewsData'@ObservedV2
class NewsModel {@Trace newsData: Array<NewsData> = []@Trace currentPage: number = 1@Trace pageSize: number = Const.PAGE_SIZE@Trace pullDownRefreshText: Resource = $r('app.string.pull_down_refresh_text')@Trace pullDownRefreshImage: Resource = $r('app.media.ic_pull_down_refresh')@Trace pullDownRefreshHeight: number = Const.CUSTOM_LAYOUT_HEIGHT@Trace isVisiblePullDown: boolean = false@Trace pullUpLoadText: Resource = $r('app.string.pull_up_load_text')@Trace pullUpLoadImage: Resource = $r('app.media.ic_pull_up_load')@Trace pullUpLoadHeight: number = Const.CUSTOM_LAYOUT_HEIGHT@Trace isVisiblePullUpLoad: boolean = false@Trace offsetY: number = 0@Trace pageState: number = PageState.Loading@Trace hasMore: boolean = true@Trace startIndex = 0@Trace endIndex = 0@Trace downY = 0@Trace lastMoveY = 0@Trace isRefreshing: boolean = false@Trace isCanRefresh = false@Trace isPullRefreshOperation = false@Trace isLoading: boolean = false@Trace isCanLoadMore: boolean = false
}export default NewsModel

七、下拉刷新和上拉加載

  1. 給列表添加事件
// entry/src/main/ets/view/NewsList.ets
// ...
import { listTouchEvent } from '../common/utils/PullDownRefresh'
@ComponentV2
export default struct NewsList {// .../*** 構建新聞列表 UI。*/build() {Column() {// ...}// ....onTouch((event: TouchEvent | undefined) => {if (event) {if (this.newsModel.pageState === PageState.Success) {listTouchEvent(this.newsModel, event)}}})}// ...
}
  1. 下拉刷新
// entry/src/main/ets/common/utils/PullDownRefresh.ets
import { promptAction } from '@kit.ArkUI'
import { touchMoveLoadMore, touchUpLoadMore } from './PullUpLoadMore'
import {CommonConstant as Const,RefreshState
} from '../constant/CommonConstant'
import NewsViewModel from '../../viewmodel/NewsViewModel'
import { NewsData } from '../../viewmodel/NewsData'
import NewsModel from '../../viewmodel/NewsModel'/*** 處理列表的觸摸事件。* @param that 當前新聞數據模型。* @param event 觸摸事件對象。*/
export function listTouchEvent(that: NewsModel, event: TouchEvent) {switch (event.type) {case TouchType.Down:// 記錄手指按下時的 Y 軸坐標that.downY = event.touches[0].ythat.lastMoveY = event.touches[0].ybreakcase TouchType.Move:// 如果當前處于刷新或加載狀態,則直接返回if ((that.isRefreshing === true) || (that.isLoading === true)) {return}let isDownPull = event.touches[0].y - that.lastMoveY > 0if (((isDownPull === true) || (that.isPullRefreshOperation === true)) && (that.isCanLoadMore === false)) {// 手指向下滑動,處理下拉刷新touchMovePullRefresh(that, event)} else {// 手指向上滑動,處理上拉加載更多touchMoveLoadMore(that, event)}that.lastMoveY = event.touches[0].ybreakcase TouchType.Cancel:breakcase TouchType.Up:// 處理手指抬起時的刷新或加載邏輯if ((that.isRefreshing === true) || (that.isLoading === true)) {return}if ((that.isPullRefreshOperation === true)) {// 觸發下拉刷新touchUpPullRefresh(that)} else {// 處理上拉加載更多touchUpLoadMore(that)}breakdefault:break}
}/*** 處理下拉刷新時的手指移動事件。* @param that 當前新聞數據模型。* @param event 觸摸事件對象。*/
export function touchMovePullRefresh(that: NewsModel, event: TouchEvent) {if (that.startIndex === 0) {that.isPullRefreshOperation = truelet height = vp2px(that.pullDownRefreshHeight)that.offsetY = event.touches[0].y - that.downY// 判斷是否達到刷新條件if (that.offsetY >= height) {pullRefreshState(that, RefreshState.Release)that.offsetY = height + that.offsetY * Const.Y_OFF_SET_COEFFICIENT} else {pullRefreshState(that, RefreshState.DropDown)}if (that.offsetY < 0) {that.offsetY = 0that.isPullRefreshOperation = false}}
}/*** 處理手指抬起后的下拉刷新操作。* @param that 當前新聞數據模型。*/
export function touchUpPullRefresh(that: NewsModel) {if (that.isCanRefresh === true) {that.offsetY = vp2px(that.pullDownRefreshHeight)pullRefreshState(that, RefreshState.Refreshing)that.currentPage = 1setTimeout(() => {let self = thatNewsViewModel.getNewsList(that.currentPage, that.pageSize, Const.GET_NEWS_LIST).then((data: NewsData[]) => {if (data.length === that.pageSize) {self.hasMore = trueself.currentPage++} else {self.hasMore = false}self.newsData = datacloseRefresh(self, true)}).catch((err: string | Resource) => {promptAction.showToast({ message: err })closeRefresh(self, false)})}, Const.DELAY_TIME)} else {closeRefresh(that, false)}
}/*** 設置下拉刷新的狀態。* @param that 當前新聞數據模型。* @param state 下拉刷新的狀態值。*/
export function pullRefreshState(that: NewsModel, state: number) {switch (state) {case RefreshState.DropDown:that.pullDownRefreshText = $r('app.string.pull_down_refresh_text')that.pullDownRefreshImage = $r("app.media.ic_pull_down_refresh")that.isCanRefresh = falsethat.isRefreshing = falsethat.isVisiblePullDown = truebreakcase RefreshState.Release:that.pullDownRefreshText = $r('app.string.release_refresh_text')that.pullDownRefreshImage = $r("app.media.ic_pull_up_refresh")that.isCanRefresh = truethat.isRefreshing = falsebreakcase RefreshState.Refreshing:that.offsetY = vp2px(that.pullDownRefreshHeight)that.pullDownRefreshText = $r('app.string.refreshing_text')that.pullDownRefreshImage = $r("app.media.ic_pull_up_load")that.isCanRefresh = truethat.isRefreshing = truebreakcase RefreshState.Success:that.pullDownRefreshText = $r('app.string.refresh_success_text')that.pullDownRefreshImage = $r("app.media.ic_succeed_refresh")that.isCanRefresh = truethat.isRefreshing = truebreakcase RefreshState.Fail:that.pullDownRefreshText = $r('app.string.refresh_fail_text')that.pullDownRefreshImage = $r("app.media.ic_fail_refresh")that.isCanRefresh = truethat.isRefreshing = truebreakdefault:break}
}/*** 關閉下拉刷新動畫。* @param that 當前新聞數據模型。* @param isRefreshSuccess 是否刷新成功。*/
export function closeRefresh(that: NewsModel, isRefreshSuccess: boolean) {let self = thatsetTimeout(() => {let delay = Const.RefreshConstant_DELAY_PULL_DOWN_REFRESHif (self.isCanRefresh === true) {pullRefreshState(that, isRefreshSuccess ? RefreshState.Success : RefreshState.Fail)delay = Const.RefreshConstant_DELAY_SHRINK_ANIMATION_TIME}animateTo({duration: Const.RefreshConstant_CLOSE_PULL_DOWN_REFRESH_TIME,delay: delay,onFinish: () => {pullRefreshState(that, RefreshState.DropDown)self.isVisiblePullDown = falseself.isPullRefreshOperation = false}}, () => {self.offsetY = 0})}, self.isCanRefresh ? Const.DELAY_ANIMATION_DURATION : 0)
}

關鍵代碼解讀:

  • 創建一個下拉刷新布局CustomLayout,動態傳入刷新圖片和刷新文字描述。
  • 將下拉刷新的布局添加到NewsList.ets文件中新聞列表布局ListLayout里面,監聽ListLayout組件的onTouch事件實現下拉刷新。
  • 在onTouch事件中,listTouchEvent方法判斷觸摸事件是否滿足下拉條件。
  • 在touchMovePullRefresh方法中,對下拉的偏移量與下拉刷新布局的高度進行對比,如果大于布局高度并且在新聞列表的頂部,則表示達到刷新條件。
  • 在pullRefreshState方法中,對下拉刷新布局中的狀態圖片和描述進行改變。當手指松開,才執行刷新操作。
  1. 上拉加載更多
// entry/src/main/ets/common/utils/PullUpLoadMore.ets
import { promptAction } from '@kit.ArkUI'
import { CommonConstant as Const } from '../constant/CommonConstant'
import NewsViewModel from '../../viewmodel/NewsViewModel'
import { NewsData } from '../../viewmodel/NewsData'
import NewsModel from '../../viewmodel/NewsModel'/*** 處理手指移動時的加載更多邏輯。* 當用戶滑動到列表底部,并且滑動距離足夠時,觸發加載更多。** @param that 當前新聞數據模型。* @param event 觸摸事件對象。*/
export function touchMoveLoadMore(that: NewsModel, event: TouchEvent) {if (that.endIndex === that.newsData.length - 1 || that.endIndex === that.newsData.length) {// 計算滑動偏移量that.offsetY = event.touches[0].y - that.downY// 判斷是否滑動超過一定閾值,觸發加載更多if (Math.abs(that.offsetY) > vp2px(that.pullUpLoadHeight) / 2) {that.isCanLoadMore = truethat.isVisiblePullUpLoad = truethat.offsetY = -vp2px(that.pullUpLoadHeight) + that.offsetY * Const.Y_OFF_SET_COEFFICIENT}}
}/*** 處理手指抬起時的加載更多邏輯。* 如果滿足加載條件并且還有更多數據,則加載新的新聞數據。** @param that 當前新聞數據模型。*/
export function touchUpLoadMore(that: NewsModel) {let self = that// 執行滑動動畫,重置偏移量animateTo({duration: Const.ANIMATION_DURATION,}, () => {self.offsetY = 0})// 如果可以加載更多并且還有數據可加載if ((self.isCanLoadMore === true) && (self.hasMore === true)) {self.isLoading = true// 模擬網絡請求,延遲加載數據setTimeout(() => {closeLoadMore(that)NewsViewModel.getNewsList(self.currentPage, self.pageSize, Const.GET_NEWS_LIST).then((data: NewsData[]) => {if (data.length === self.pageSize) {self.currentPage++self.hasMore = true} else {self.hasMore = false}// 追加新數據到新聞列表self.newsData = self.newsData.concat(data)}).catch((err: string | Resource) => {// 處理請求失敗情況,顯示錯誤提示promptAction.showToast({ message: err })})}, Const.DELAY_TIME)} else {// 關閉加載更多動畫closeLoadMore(self)}
}/*** 關閉加載更多動畫,重置相關狀態。** @param that 當前新聞數據模型。*/
export function closeLoadMore(that: NewsModel) {that.isCanLoadMore = falsethat.isLoading = falsethat.isVisiblePullUpLoad = false
}

八、服務端搭建流程

  1. 搭建nodejs環境:本篇Codelab的服務端是基于nodejs實現的,需要安裝nodejs,如果您本地已有nodejs環境可以跳過此步驟。
    1. 檢查本地是否安裝nodejs:打開命令行工具(如Windows系統的cmd和Mac電腦的Terminal,這里以Mac為例),輸入node -v,如果可以看到版本信息,說明已經安裝nodejs。

    1. 如果本地沒有nodejs環境,您可以去nodejs官網上下載所需版本進行安裝配置。
    2. 配置完環境變量后,重新打開命令行工具,輸入node -v,如果可以看到版本信息,說明已安裝成功。
  1. 構建局域網環境:測試本Codelab時要確保運行服務端代碼的電腦和測試機連接的是同一局域網下的網絡,您可以用您的手機開一個個人熱點,然后將測試機和運行服務端代碼的電腦都連接您的手機熱點進行測試。
  2. 運行服務端代碼:在本項目的HttpServerOfNews目錄下打開命令行工具,輸入npm install 安裝服務端依賴包,安裝成功后輸入npm start點擊回車。看到“服務器啟動成功!”則表示服務端已經在正常運行。

  1. 連接服務器地址:打開命令行工具,Mac電腦輸入ifconfig,Windows電腦輸入ipconfig命令查看本地ip,將本地ip地址和端口號(默認3000),添加到src/main/ets/common/constant/CommonConstants.ets文件里:
// entry/src/main/ets/common/constant/CommonCanstants.ets
export class CommonConstant {/*** 服務器的主機地址*/static readonly SERVER: string = 'http://192.168.31.150:3000'// ...
}// ...

九、視頻和代碼資源

資源請訪問:《HarmonyOS應用開發實戰指南(進階篇)》

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/83053.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/83053.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/83053.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

辦公效率王Word批量轉PDF 50 +文檔一鍵轉換保留原格式零錯亂

各位辦公小能手們&#xff0c;我跟你們說啊&#xff01;在辦公的時候&#xff0c;咱經常會碰到要把一堆Word文檔轉成PDF格式的情況&#xff0c;比如說要統一文件格式、保護文檔內容或者方便分享啥的。這時候&#xff0c;就需要用到Word批量轉換成PDF的軟件啦。下面我就給你們好…

一張Billing項目的流程圖

流程圖 工作記錄 2016-11-11 序號 工作 相關人員 1 修改Payment Posted的導出。 Claim List的頁面加了導出。 Historical Job 加了Applied的顯示和詳細。 郝 識別引擎監控 Ps (iCDA LOG :剔除了160篇ASG_BLANK之后的結果): LOG_File 20161110.txt BLANK_CDA/ALL 45/10…

SpringAI系列4: Tool Calling 工具調用 【感覺這版本有bug】

前言&#xff1a;在最近發布的 Spring AI 1.0.0.M6 版本中&#xff0c;其中一個重大變化是 Function Calling 被廢棄&#xff0c;被 Tool Calling 取代。Tool Calling工具調用&#xff08;也稱為函數調用&#xff09;是AI應用中的常見模式&#xff0c;允許模型通過一組API或工具…

第六十三節:深度學習-模型推理與后處理

深度學習模型訓練完成后,如何高效地將其部署到實際應用中并進行準確預測?這正是模型推理與后處理的核心任務。OpenCV 的 dnn 模塊為此提供了強大支持,本文將深入探討 OpenCV 在深度學習模型推理與后處理中的關鍵技術與實踐。 第一部分:基礎概念與環境搭建 1.1 核心概念解析…

uniapp開發企業微信小程序時 wx.qy.login 在uniapp中使用的時候,需要導包嗎?

在 UniApp 中使用 “wx.qy.login” 不需要手動導包&#xff0c;但需要滿足以下條件&#xff1a; 一、環境要求與配置 1&#xfffd; 企業微信環境判斷 必須確保當前運行環境是企業微信客戶端&#xff0c;通過 “uni.getSystemInfoSync().environment” 判斷是否為 “wxwork”…

ONLYOFFICE文檔API:更強的安全功能

在數字化辦公時代&#xff0c;文檔的安全性與隱私保護已成為企業和個人用戶的核心關切。如何確保信息在存儲、傳輸及協作過程中的安全&#xff0c;是開發者與IT管理者亟需解決的問題。ONLYOFFICE作為一款功能強大的開源辦公套件&#xff0c;不僅提供了高效的文檔編輯與協作體驗…

Linux系統編程之共享內存

概述 在Linux系統中&#xff0c;共享內存也是一種高效的進程間通信機制&#xff0c;允許兩個或多個進程共享同一塊物理內存區域。通過這種方式&#xff0c;不同進程可以直接訪問和操作相同的數據&#xff0c;從而避免了數據的復制。由于數據直接在內存中共享&#xff0c;沒有額…

零知開源——STM32F407VET6驅動Flappy Bird游戲教程

簡介 本教程使用STM32F407VET6零知增強板驅動3.5寸TFT觸摸屏實現經典Flappy Bird游戲。通過觸摸屏控制小鳥跳躍&#xff0c;躲避障礙物柱體&#xff0c;挑戰最高分。項目涉及STM32底層驅動、圖形庫移植、觸摸控制和游戲邏輯設計。 目錄 簡介 一、硬件準備 二、軟件架構 三、…

Elasticsearch創建快照倉庫報錯處理

創建快照倉庫報錯&#xff1a; 根據報錯提示的信息&#xff0c;問題可能出在 Elasticsearch 的配置中。當你嘗試創建一個文件系統&#xff08;fs&#xff09;類型的快照倉庫時&#xff0c;雖然已經指定了 location 參數&#xff0c;但 Elasticsearch 仍然報錯&#xff0c;這通…

服務器如何配置防火墻管理端口訪問?

配置服務器防火墻來管理端口訪問&#xff0c;是保障云服務器安全的核心步驟。下面我將根據你使用的不同操作系統&#xff08;Linux: Ubuntu/Debian/CentOS&#xff1b;Windows Server&#xff09;介紹常用防火墻配置方法。 ? 一、Linux 防火墻配置&#xff08;UFW / firewalld…

Redis最佳實踐——安全與穩定性保障之連接池管理詳解

Redis 在電商應用的連接池管理全面詳解 一、連接池核心原理與架構 1. 連接池工作模型 #mermaid-svg-G7I3ukCljlJZAXaA {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-G7I3ukCljlJZAXaA .error-icon{fill:#552222;}…

打印機無法遠程打印?可以本地打印,本地網絡打印機設置給異地使用

很多小伙伴常有打印、遠程打印的需求&#xff0c;特別是對于電商人、跨境電商、教師、產品經理、實驗人員等群體來說掌握這項技能可謂是能夠在很多場景下帶來便捷&#xff0c;大幅提升做事效率&#xff01;打印機是家庭和企業經常用到的設備&#xff0c;很多情況下會遇到本地可…

【Linux】進程地址空間揭秘(初步認識)

10.進程地址空間&#xff08;初步認識&#xff09; 文章目錄 10.進程地址空間&#xff08;初步認識&#xff09;一、進程地址空間的實驗現象解析二、進程地址空間三、虛擬內存管理補充&#xff1a;數據的寫時拷貝&#xff08;淺談&#xff09;補充&#xff1a;頁表&#xff08;…

深入探討redis:主從復制

前言 如果某個服務器程序&#xff0c;只部署在一個物理服務器上就可能會面臨一下問題(單點問題) 可用性問題&#xff0c;如果這個機器掛了&#xff0c;那么對應的客戶端服務也相繼斷開性能/支持的并發量有限 所以為了解決這些問題&#xff0c;就要引入分布式系統&#xff0c…

MacOS安裝Docker Desktop并漢化

1. 安裝Docker Desktop 到Docker Desktop For Mac下載對應系統的Docker Desktop 安裝包&#xff0c;下載后安裝&#xff0c;沒有賬號需要注冊&#xff0c;然后登陸即可。 2. 漢化 前往漢化包下載鏈接下載對應系統的.asar文件 然后將安裝好的文件覆蓋原先的文件app.asar文件…

索引的選擇與Change Buffer

1. 索引選擇與Change Buffer 問題引出&#xff1a;普通索引 vs 唯一索引 ——如何選擇&#xff1f; 在實際業務中&#xff0c;如果一個字段的值天然具有唯一性&#xff08;如身份證號&#xff09;&#xff0c;并且業務代碼已確保無重復寫入&#xff0c;那就存在兩種選擇&…

lua注意事項

感覺是lua的一大坑啊&#xff0c;它還不如函數內部就局部變量呢 注意函數等內部&#xff0c;全部給加上local得了

【多線程初階】死鎖的產生 如何避免死鎖

文章目錄 關于死鎖一.死鎖的三種情況1.一個線程,一把鎖,連續多次加鎖2.兩個線程,兩把鎖3.N個線程,M把鎖 --哲學家就餐問題 二.如何避免死鎖死鎖是如何構成的(四個必要條件)打破死鎖 三.死鎖小結 關于死鎖 一.死鎖的三種情況 1.一個線程,一把鎖,連續多次加鎖 -->由synchroni…

【NLP基礎知識系列課程-Tokenizer的前世今生第二課】NLP 中的 Tokenizer 技術發展史

從詞表到子詞&#xff1a;Tokenizer 的“進化樹” 我們常說“語言模型是理解人類語言的工具”&#xff0c;但事實上&#xff0c;模型能不能“理解”&#xff0c;關鍵要看它接收到了什么樣的輸入。而 Tokenizer&#xff0c;就是這一輸入階段的設計者。 在 NLP 的發展歷程中&am…

Rust 學習筆記:循環和迭代器的性能比較

Rust 學習筆記&#xff1a;循環和迭代器的性能比較 Rust 學習筆記&#xff1a;循環和迭代器的性能比較示例 1示例 2總結 Rust 學習筆記&#xff1a;循環和迭代器的性能比較 示例 1 我們運行一個基準測試&#xff0c;將《福爾摩斯探案集》的全部內容加載到一個字符串中&#x…