Initial commit: @b2c/first_page HarmonyOS stage-mode HAR
## Repository - HarmonyOS stage-mode HAR module, consumed by host app - No standalone build — hvigorw, oh_modules, and ../biz_quote absent here ## Architecture ### Feed Cards - FirstPage.createNodeView dispatches cards by FirstPageConstant.KEY_* - Card config from first_page_cards_config.json loaded by FirstPageCardsConfigLoader - Public entrypoint: Index.ets ### Market Ranking (src/main/ets/market-ranking/) - HTTP: MarketRankingDataFetcher fetches recommend_futures contracts - HQ: MarketRankingHqRequestClient subscribes via 4106/frame 2201/scenario qht_qihuo_sort - TCP reconnect rebuilds subscription from original HTTP contract list - Async callbacks guarded by request/subscription versions ### Other Cards - HotList, CommodityOptions, AiDiagnosis, AiRiseFall, SelfSelectedStock, CustomEntryList, FourEntryList - Each has DataFetcher + HQ request client + NodeComponent ## Key Changes (from CardHarmonyOS reference) - Added MetricId, PeriodId, SortOrder enums (MarketRankingModels.ets) - Replaced string comparisons with enum switches in thirdColumnTitle/Value/Color - Removed redundant undefined guards in thirdColumnColor/priceColor, delegated to riseFallColor - Inlined priceColor(), use riseFallColor directly - Tab bar Stack.height(30), Alignment.End (reverted from 46/TopEnd) - Name column fontWeight removed (default regular)
This commit is contained in:
@@ -0,0 +1,413 @@
|
||||
import { AdsPageConfig, AppInfoConfig, FirstPageTabManager, FrameId, GlobalContext } from 'biz_common'
|
||||
import { CustomEntryListNodeComponent } from '../../node/view/CustomEntryListNodeComponent'
|
||||
import FirstPageTitleBar from '../../views/FirstPageTitleBar'
|
||||
import { BannerNodeComponent } from '../../node/view/BannerNodeComponent'
|
||||
import { FirstPageDataFetcher } from '../../datacenter/FirstPageDataFetcher'
|
||||
import { FirstPageNodeModel } from '../../datacenter/model/FirstPageNodeModel'
|
||||
import { FourEntryListNodeComponent } from '../../node/view/FourEntryListNodeComponent'
|
||||
import { FirstPageConstant } from '../../datacenter/FirstPageConstant'
|
||||
import { TopImageNodeComponent } from '../../node/view/TopImageNodeComponent'
|
||||
import { TITLE_BAR_HEIGHT } from '../../util/FirstPageConstant'
|
||||
import { FirstPageContentBg } from '../../views/FirstPageContentBg'
|
||||
import { PreferenceUtils } from 'hxutil'
|
||||
import { getResourceString } from '../../util/ResourceUtil'
|
||||
import { router } from '@kit.ArkUI'
|
||||
import { AppUtil } from '@pura/harmony-utils'
|
||||
import { SelfSelectedStockNodeComponent } from '../../node/view/SelfSelectedStockNodeComponent'
|
||||
import { FirstPagePopupAdHelper } from '../../node/helper/FirstPagePopupAdHelper'
|
||||
import { OnAdsDataUpdateListener, BaseAd, AdsManager, ADS_TYPE_INDEX_POPUP, AdsPopupManager } from '@b2c-f/fuhm-ads'
|
||||
import { DebugToolUtil } from 'biz_debug'
|
||||
import { KaihuFlagManager, NewUserManager, UserSourceManager } from 'biz_hxservice'
|
||||
import { AdsPopupHost } from '../../popup/AdsPopupHost'
|
||||
import { AiDiagnosisNodeComponent } from '../../node/view/AiDiagnosisNodeComponent'
|
||||
import { AiRiseFallNodeComponent } from '../../node/view/AiRiseFallNodeComponent'
|
||||
import { HotListNodeComponent } from '../../node/view/HotListNodeComponent'
|
||||
import { CommodityOptionsNodeComponent } from '../../node/view/CommodityOptionsNodeComponent'
|
||||
import { MarketRankingNodeComponent } from '../../market-ranking/MarketRankingNodeComponent'
|
||||
|
||||
|
||||
/**
|
||||
* 首页
|
||||
* @author wubingsong@myhexin.com
|
||||
*/
|
||||
@Component
|
||||
export struct FirstPage {
|
||||
//首页tab管理器
|
||||
@ObjectLink @Watch('onVisibilityChange') tabManager: FirstPageTabManager;
|
||||
@Provide firstPageVisible: boolean = true;
|
||||
@State nodeModelArr: FirstPageNodeModel[] = []
|
||||
private topBarHeight: number = 0
|
||||
private scrollHeight: number = 0
|
||||
@State isTopViewShow: boolean = false
|
||||
@State isRefreshing: boolean = false
|
||||
private refreshTimer: number = -1
|
||||
@State isStartRefresh: boolean = true
|
||||
@State refreshTitle: string = '下拉可以刷新'
|
||||
@State refreshTime: string = ''
|
||||
@State refreshRotateAngle: number = 0
|
||||
@State needAnimation: boolean = false
|
||||
/** 广告数据监听器 */
|
||||
private adsPopupListener: OnAdsDataUpdateListener = {
|
||||
onAdsDataUpdate: (adsType: string, adsDataModels: BaseAd[]) => {
|
||||
if (this.firstPageVisible) {
|
||||
FirstPagePopupAdHelper.getInstance().onAdsDataUpdate(adsType, adsDataModels)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 卡片刷新触发器
|
||||
@State refreshTrigger: number = 0
|
||||
// 自动刷新定时器 ID
|
||||
private autoRefreshTimer: number = -1
|
||||
// 自动刷新间隔(60秒)
|
||||
private readonly AUTO_REFRESH_INTERVAL = 60000
|
||||
private kaihuFlagListener = (): void => {
|
||||
const kaihuFlagManager = KaihuFlagManager.getInstance()
|
||||
//归宿 更新了加载一遍首页和广告
|
||||
if (kaihuFlagManager.isFlagChanged()) {
|
||||
kaihuFlagManager.resetFlagChanged()
|
||||
this.refreshFirstPageDataAndAds()
|
||||
}
|
||||
}
|
||||
private userSourceUpdateListener = (): void => {
|
||||
//来源更新后,只有当没有归属时,才重新刷新首页和广告
|
||||
if (!KaihuFlagManager.getInstance().getKaihuFlag()) {
|
||||
this.refreshFirstPageDataAndAds()
|
||||
}
|
||||
}
|
||||
|
||||
onVisibilityChange() {
|
||||
this.firstPageVisible = this.tabManager.tabVisibility;
|
||||
FirstPagePopupAdHelper.getInstance().firstPageVisible = this.firstPageVisible;
|
||||
if (this.tabManager.tabVisibility) {
|
||||
FirstPageDataFetcher.getInstance().remoteFetchData()
|
||||
FirstPagePopupAdHelper.getInstance().showPopupAdWhenOnResume()
|
||||
this.refreshAdsData()
|
||||
AdsManager.refreshNotifyAds(ADS_TYPE_INDEX_POPUP)
|
||||
AdsPopupHost.getInstance().onPageJump(AdsPageConfig.FIRST_PAGE_ID)
|
||||
// 启动定时器
|
||||
this.startAutoRefresh()
|
||||
} else {
|
||||
KaihuFlagManager.getInstance().removeListener(this.kaihuFlagListener)
|
||||
UserSourceManager.getInstance().removeListener(this.userSourceUpdateListener)
|
||||
AdsPopupHost.getInstance().onPageJump(AdsPageConfig.INVALID)
|
||||
// 暂停定时器
|
||||
this.stopAutoRefresh()
|
||||
this.stopDelayRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
private refreshAdsData() {
|
||||
KaihuFlagManager.getInstance().addListener(this.kaihuFlagListener)
|
||||
UserSourceManager.getInstance().addListener(this.userSourceUpdateListener)
|
||||
if (KaihuFlagManager.getInstance().isFlagChanged()) {
|
||||
//用户开户标志如果有更新此时发送首页请求了则将其复原重置,用来防止没有变化却重复发送请求
|
||||
KaihuFlagManager.getInstance().resetFlagChanged()
|
||||
}
|
||||
if (NewUserManager.getInstance().needRequestAdsNow()) {
|
||||
//发送请求首页广告
|
||||
this.refreshFirstPageDataAndAds()
|
||||
}
|
||||
}
|
||||
|
||||
aboutToAppear(): void {
|
||||
this.firstPageVisible = this.tabManager.tabVisibility;
|
||||
FirstPagePopupAdHelper.getInstance().firstPageVisible = this.firstPageVisible;
|
||||
this.topBarHeight = px2vp(GlobalContext.statusBarHeight) + TITLE_BAR_HEIGHT
|
||||
if (DebugToolUtil.isInnerNetSimulateMode()) {
|
||||
// 内网请求不到首页板块,默认只填充自选盯盘
|
||||
const hqNode = new FirstPageNodeModel()
|
||||
hqNode.id = 0
|
||||
hqNode.targetTypeId = FirstPageConstant.KEY_HANGQING_TABLE.toString()
|
||||
this.nodeModelArr = [hqNode]
|
||||
}
|
||||
FirstPageDataFetcher.getInstance().nodeConfigChange = (response) => {
|
||||
this.finishRefresh()
|
||||
if (response != null) {
|
||||
for (let index = 0; index < response.length; index++) {
|
||||
const model = response[index];
|
||||
if (model.targetTypeId == FirstPageConstant.KEY_TOP_BG.toString()) {
|
||||
if (index > 0) {
|
||||
let topModel = response.splice(index, 1)
|
||||
response.unshift(...topModel)
|
||||
}
|
||||
this.isTopViewShow = true
|
||||
break
|
||||
} else if (index == response.length - 1) {
|
||||
this.isTopViewShow = false
|
||||
}
|
||||
}
|
||||
let hotListModel = new FirstPageNodeModel()
|
||||
hotListModel.targetTypeId = FirstPageConstant.KEY_HOT_LIST.toString()
|
||||
let commodityOptionModel = new FirstPageNodeModel()
|
||||
commodityOptionModel.targetTypeId = FirstPageConstant.KEY_COMMODITY_OPTIONS.toString()
|
||||
let aiDiagnosisModel = new FirstPageNodeModel()
|
||||
aiDiagnosisModel.targetTypeId = FirstPageConstant.KEY_AI_DIAGNOSIS.toString()
|
||||
let aiRiseFallModel = new FirstPageNodeModel()
|
||||
aiRiseFallModel.targetTypeId = FirstPageConstant.KEY_AI_RISEFALL.toString()
|
||||
let marketRankingModel = new FirstPageNodeModel()
|
||||
marketRankingModel.targetTypeId = FirstPageConstant.KEY_MARKET_RANKING.toString()
|
||||
let feedBackModel = new FirstPageNodeModel()
|
||||
feedBackModel.targetTypeId = FirstPageConstant.KEY_FEED_BACK.toString()
|
||||
let endTipModel = new FirstPageNodeModel()
|
||||
endTipModel.targetTypeId = FirstPageConstant.KEY_END_TIP.toString()
|
||||
response.push(hotListModel, commodityOptionModel, aiRiseFallModel, aiDiagnosisModel, marketRankingModel, feedBackModel, endTipModel)
|
||||
this.nodeModelArr = response
|
||||
}
|
||||
}
|
||||
FirstPageDataFetcher.getInstance().onError = () => {
|
||||
this.isRefreshing = false
|
||||
}
|
||||
FirstPageDataFetcher.getInstance().fetchDataLocal()
|
||||
FirstPageDataFetcher.getInstance().remoteFetchData()
|
||||
this.refreshAdsData()
|
||||
AdsManager.addAdsDataUpdateListener(ADS_TYPE_INDEX_POPUP, this.adsPopupListener)
|
||||
if (this.firstPageVisible) {
|
||||
AdsManager.refreshNotifyAds(ADS_TYPE_INDEX_POPUP)
|
||||
}
|
||||
// 启动ai卡片定时器
|
||||
this.startAutoRefresh()
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动自动刷新定时器
|
||||
*/
|
||||
private startAutoRefresh(): void {
|
||||
if (this.autoRefreshTimer !== -1) {
|
||||
return
|
||||
}
|
||||
this.autoRefreshTimer = setInterval(() => {
|
||||
this.refreshTrigger++
|
||||
}, this.AUTO_REFRESH_INTERVAL)
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止自动刷新定时器
|
||||
*/
|
||||
private stopAutoRefresh(): void {
|
||||
if (this.autoRefreshTimer !== -1) {
|
||||
clearInterval(this.autoRefreshTimer)
|
||||
this.autoRefreshTimer = -1
|
||||
}
|
||||
}
|
||||
|
||||
private startDelayRefresh(): void {
|
||||
this.refreshTimer = setTimeout(() => {
|
||||
this.isRefreshing = false
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
private stopDelayRefresh(): void {
|
||||
if (this.refreshTimer !== -1) {
|
||||
clearTimeout(this.refreshTimer)
|
||||
this.refreshTimer = -1
|
||||
}
|
||||
}
|
||||
|
||||
aboutToDisappear(): void {
|
||||
AdsManager.removeAdsDataUpdateListener(ADS_TYPE_INDEX_POPUP, this.adsPopupListener)
|
||||
// 停止自动刷新定时器
|
||||
this.stopAutoRefresh()
|
||||
this.stopDelayRefresh()
|
||||
}
|
||||
|
||||
private refreshFirstPageDataAndAds(): void {
|
||||
AdsManager.refreshNotifyFirstPageAds(true)
|
||||
}
|
||||
|
||||
private finishRefresh() {
|
||||
this.refreshTime = PreferenceUtils.getStringSync(GlobalContext.get(), 'SpFirstPage', 'refreshTime', '')
|
||||
// 刷新完成后递增卡片刷新触发器
|
||||
this.refreshTrigger++
|
||||
this.startDelayRefresh()
|
||||
}
|
||||
build() {
|
||||
Refresh({ refreshing: $$this.isRefreshing, builder: this.updateRefreshHeader()}) {
|
||||
Stack({ alignContent: Alignment.TopStart }) {
|
||||
FirstPageTitleBar().zIndex(5)
|
||||
Scroll() {
|
||||
Stack({ alignContent: Alignment.TopStart }) {
|
||||
FirstPageContentBg()
|
||||
.visibility(this.isTopViewShow ? Visibility.None : Visibility.Visible)
|
||||
Column() {
|
||||
Column()
|
||||
.width('100%')
|
||||
.height(this.topBarHeight)
|
||||
.visibility(this.isTopViewShow ? Visibility.None : Visibility.Visible)
|
||||
|
||||
ForEach(this.nodeModelArr, (model: FirstPageNodeModel, index: number) => {
|
||||
this.createNodeView(model, index)
|
||||
})
|
||||
}
|
||||
.margin({bottom: 10})
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.scrollBar(BarState.Off)
|
||||
.width('100%')
|
||||
.height('auto')
|
||||
.backgroundColor($r('app.color.surface_layer1_background'))
|
||||
.onScrollStart(()=>{
|
||||
AdsPopupHost.getInstance().onScrollEvent()
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
}
|
||||
.width('100%')
|
||||
.height('100%')
|
||||
.refreshOffset(90)
|
||||
.pullToRefresh(true)
|
||||
.onStateChange((refreshStatus: RefreshStatus) => {
|
||||
if (refreshStatus == RefreshStatus.Drag) {
|
||||
this.refreshTitle = getResourceString(getContext(),$r('app.string.drag_refresh_pull'));
|
||||
this.needAnimation = false
|
||||
this.isStartRefresh = true
|
||||
this.refreshRotateAngle = 0
|
||||
} else if (refreshStatus == RefreshStatus.OverDrag) {
|
||||
this.refreshTitle = getResourceString(getContext(),$r('app.string.drag_refresh_to_refresh'));
|
||||
this.needAnimation = true
|
||||
this.refreshRotateAngle = 180
|
||||
} else if (refreshStatus == RefreshStatus.Refresh) {
|
||||
this.refreshTitle = getResourceString(getContext(),$r('app.string.drag_refresh_loading'));
|
||||
this.refreshTime = PreferenceUtils.getStringSync(GlobalContext.get(), 'SpFirstPage', 'refreshTime', '')
|
||||
this.isStartRefresh = false
|
||||
}
|
||||
})
|
||||
.onRefreshing(() => {
|
||||
FirstPageDataFetcher.getInstance().remoteFetchData()
|
||||
})
|
||||
}
|
||||
|
||||
@Builder
|
||||
updateRefreshHeader() {
|
||||
Row() {
|
||||
Image($r('app.media.firstpage_pull_status_arrow'))
|
||||
.width(18)
|
||||
.height(40)
|
||||
.rotate({ angle: this.refreshRotateAngle })
|
||||
.animation(this.needAnimation ? {
|
||||
duration: 500,
|
||||
} : null)
|
||||
.visibility(this.isStartRefresh ? Visibility.Visible : Visibility.None)
|
||||
|
||||
LoadingProgress()
|
||||
.width(30)
|
||||
.height(30)
|
||||
.visibility(this.isStartRefresh ? Visibility.None : Visibility.Visible)
|
||||
|
||||
Column() {
|
||||
Text(this.refreshTitle)
|
||||
.fontSize(15)
|
||||
.fontColor($r('app.color.elements_text_primary_02'))
|
||||
|
||||
Row() {
|
||||
Text($r('app.string.drag_refresh_last_update_time'))
|
||||
.fontSize(15)
|
||||
.fontColor($r('app.color.elements_text_primary_02'))
|
||||
.margin({right: 10})
|
||||
|
||||
Text(this.refreshTime)
|
||||
.fontSize(15)
|
||||
.fontColor($r('app.color.elements_text_primary_02'))
|
||||
}
|
||||
.visibility(this.refreshTime == '' ? Visibility.None : Visibility.Visible)
|
||||
}
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.alignItems(HorizontalAlign.Center)
|
||||
.margin({left: 10})
|
||||
}
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.width('100%')
|
||||
}
|
||||
|
||||
@Builder
|
||||
createNodeView(model: FirstPageNodeModel, index: number) {
|
||||
if (model.targetTypeId == FirstPageConstant.KEY_BANNER.toString()) {
|
||||
BannerNodeComponent({
|
||||
nodeModel: model
|
||||
}).margin({top: index > 0 ? 10 : 0, left: 10, right: 10})
|
||||
} else if (model.targetTypeId == FirstPageConstant.KEY_FOUR_ENTRY_LIST.toString()) {
|
||||
FourEntryListNodeComponent({
|
||||
nodeModel: model
|
||||
}).margin({top: index > 0 ? 10 : 0, left: 10, right: 10})
|
||||
} else if (model.targetTypeId == FirstPageConstant.KEY_CUSTOM_ENTRY_LIST.toString()) {
|
||||
CustomEntryListNodeComponent({
|
||||
nodeModel: model
|
||||
}).margin({top: (AppInfoConfig.Official && !this.isTopViewShow || index) > 0 ? 10 : 0, left: 10, right: 10})
|
||||
} else if (model.targetTypeId == FirstPageConstant.KEY_AI_DIAGNOSIS.toString()) {
|
||||
AiDiagnosisNodeComponent({
|
||||
nodeModel: model,
|
||||
refreshTrigger: this.refreshTrigger,
|
||||
}).margin({ top: index > 0 ? 10 : 0, left: 10, right: 10 })
|
||||
} else if (model.targetTypeId == FirstPageConstant.KEY_AI_RISEFALL.toString()) {
|
||||
AiRiseFallNodeComponent({
|
||||
nodeModel: model,
|
||||
refreshTrigger: this.refreshTrigger,
|
||||
}).margin({ top: index > 0 ? 10 : 0, left: 10, right: 10 })
|
||||
} else if (model.targetTypeId == FirstPageConstant.KEY_HOT_LIST.toString()) {
|
||||
HotListNodeComponent({
|
||||
nodeModel: model,
|
||||
refreshTrigger: this.refreshTrigger,
|
||||
}).margin({ top: index > 0 ? 10 : 0, left: 10, right: 10 })
|
||||
} else if (model.targetTypeId == FirstPageConstant.KEY_COMMODITY_OPTIONS.toString()) {
|
||||
CommodityOptionsNodeComponent({
|
||||
refreshTrigger: this.refreshTrigger,
|
||||
}).margin({ top: index > 0 ? 10 : 0, left: 10, right: 10 })
|
||||
} else if (model.targetTypeId == FirstPageConstant.KEY_MARKET_RANKING.toString()) {
|
||||
MarketRankingNodeComponent({
|
||||
refreshTrigger: this.refreshTrigger,
|
||||
}).margin({ top: index > 0 ? 10 : 0, left: 10, right: 10 })
|
||||
} else if (model.targetTypeId == FirstPageConstant.KEY_TOP_BG.toString()) {
|
||||
TopImageNodeComponent({
|
||||
nodeModel: model
|
||||
})
|
||||
} else if (model.targetTypeId == FirstPageConstant.KEY_FEED_BACK.toString()) {
|
||||
this.buildFeedBackView(index)
|
||||
}else if (model.targetTypeId == FirstPageConstant.KEY_HANGQING_TABLE.toString()){
|
||||
SelfSelectedStockNodeComponent().margin({top: index > 0 ? 10 : 0, left: 10, right: 10})
|
||||
}
|
||||
else if (model.targetTypeId == FirstPageConstant.KEY_END_TIP.toString()) {
|
||||
Text($r('app.string.drag_refresh_to_bottom'))
|
||||
.fontSize(14)
|
||||
.fontColor($r('app.color.color_a3a3a3'))
|
||||
.textAlign(TextAlign.Center)
|
||||
.width('100%')
|
||||
.margin({top: 16, bottom: 15})
|
||||
}
|
||||
}
|
||||
|
||||
@Builder
|
||||
private buildFeedBackView(index:number){
|
||||
Column(){
|
||||
Row(){
|
||||
Image($r('app.media.icon_edit_square_24')).width(16).height(16).align(Alignment.Center)
|
||||
.fillColor($r('app.color.elements_icon_primary_02'))
|
||||
Text($r('app.string.feed_back_help_text'))
|
||||
.fontSize(15)
|
||||
.fontColor($r('app.color.elements_text_primary_02'))
|
||||
.textAlign(TextAlign.Center)
|
||||
.align(Alignment.Center)
|
||||
|
||||
}.width('100%')
|
||||
.height(44)
|
||||
.justifyContent(FlexAlign.Center)
|
||||
.alignItems(VerticalAlign.Center)
|
||||
.backgroundColor($r('app.color.surface_layer1_foreground'))
|
||||
.borderRadius(10)
|
||||
.onClick((event)=>{
|
||||
router.pushUrl({ url: 'pages/WebViewComponentPage', params: {
|
||||
url:AppUtil.getContext().resourceManager.getStringSync($r('app.string.fu_feed_back_help_url')),
|
||||
title: AppUtil.getContext().resourceManager.getStringSync($r('app.string.feed_back_help_text')),
|
||||
userCookie:true,
|
||||
isLoginRefreshWeb:true,
|
||||
refreshOnWidthChange:true
|
||||
} })
|
||||
})
|
||||
|
||||
}.width('100%')
|
||||
.padding({left:10,right:10})
|
||||
.padding({top: index > 0 ? 10 : 0, left: 10, right: 10})
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user