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:
clz
2026-07-26 01:10:20 +08:00
commit af0025ce38
137 changed files with 13358 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
import { AppInfoConfig, GlobalContext } from 'biz_common'
import { TITLE_BAR_HEIGHT } from '../util/FirstPageConstant'
@Component
export struct FirstPageContentBg {
@State topHeight: number = 0
@State topContentBg: Resource = $r('app.media.firstpage_top_default_bg')
aboutToAppear(): void {
this.topHeight = px2vp(GlobalContext.statusBarHeight) + TITLE_BAR_HEIGHT + (AppInfoConfig.Official?0:150);
}
build() {
Image(this.topContentBg)
.width('100%')
.height(this.topHeight)
}
}
@@ -0,0 +1,345 @@
import { EmitterConstants, PreferenceService, RecommendContract, RecommendContractManager } from 'biz_common'
import { HXUserService } from 'biz_hxservice'
import { getSelfStock, GroupInfo, loadCustomGroups, queryWatchlistGroup, Securities } from 'service_watchlist'
import { emitter } from '@kit.BasicServicesKit'
import { SelectedIdOfWatchlistGroup } from 'biz_selfcode'
import { HXUtils } from '@b2c/lib_baseui'
//自选股分组切换
@Component
export struct FirstPageSelfCodeGroupView {
static readonly GROUPID_DEFAULT = "0_34"
static readonly GROUPID_RECOMMEND_CONTRACT = "RECOMMEND_CONTRACT"
static readonly SP_FILE_NAME_FIRST_PAGE_SELF_CODE_FILE_NAME = "sp_file_name_first_page_self_code"
static readonly SP_KEY_FIRST_PAGE_SELF_CODE_LAST_GROUPID = "sp_key_first_page_self_code_last_groupid"
@Link @Watch('onPageStatusChange') onForeground: boolean
@State groups: GroupInfo[] = []
@State @Watch('onGroupIndexChange') currentIndex: number = 0
@State showPopup: boolean = false
@Prop @Watch('onInitIndexChange') initIndex: number = 0
onSelectGroup?: (index: number) => void
onSelectGroupInfo?: (index: number, groupInfo: GroupInfo) => void
private readonly OVERLAY_WIDTH = 30
private maxGroupCount = 3
private scroller = new Scroller()
private lastGroupId: string | null = null
private itemWidthArray: number[] = []
private totalWidth: number = 0
aboutToAppear(): void {
const initCurrentIndex = this.currentIndex;
this.lastGroupId =
PreferenceService.getStringValueSync(FirstPageSelfCodeGroupView.SP_FILE_NAME_FIRST_PAGE_SELF_CODE_FILE_NAME,
FirstPageSelfCodeGroupView.SP_KEY_FIRST_PAGE_SELF_CODE_LAST_GROUPID, "")
this.subscribeAllEvent()
this.initGroupInfo()
if (this.groups.length === 0 && !this.isTemporaryUser()) {
queryWatchlistGroup()
}
this.findAndInitInitIndex()
if (this.initIndex > 0 && this.groups.length > this.initIndex) {
this.selectIndex(this.initIndex)
}
if (initCurrentIndex == this.currentIndex) {
//说明第一次初始化,调用一次变化
this.onGroupIndexChange()
}
}
findAndInitInitIndex() {
if (this.lastGroupId && this.lastGroupId.length > 0) {
this.groups.forEach((groupInfo, index) => {
if (groupInfo.id === this.lastGroupId) {
this.initIndex = index
}
})
}
}
subscribeGroupStocksChang() {
emitter.on({
eventId: EmitterConstants.SELF_STOCK_GROUP_FILTER_CHANGE
}, this.refreshAction)
}
userNameChanged() {
//
}
subscribeSelfCodeQuerySucceededEvent() {
emitter.on({
eventId: EmitterConstants.SELFCODE_UGC_QUERY_SUCCEEDED
}, this.refreshAction);
}
subscribeGroupQuerySucceeded() {
emitter.on({
eventId: EmitterConstants.SELFGROUP_UPDATE_QUERY_SUCCEEDED
}, this.refreshAction)
}
subscribeAllEvent() {
this.subscribeSelfCodeQuerySucceededEvent()
this.subscribeLoginEvent()
this.subscribeGroupQuerySucceeded()
this.subscribeGroupStocksChang()
}
unSubscribe() {
emitter.off(EmitterConstants.SELFCODE_UGC_QUERY_SUCCEEDED, this.refreshAction)
emitter.off(EmitterConstants.SELFGROUP_UPDATE_QUERY_SUCCEEDED, this.refreshAction)
emitter.off(EmitterConstants.SELF_STOCK_GROUP_FILTER_CHANGE, this.refreshAction)
this.unSubscribeLoginEvent()
}
unSubscribeGroupQuerySucceeded() {
emitter.off(EmitterConstants.SELFGROUP_UPDATE_QUERY_SUCCEEDED, this.refreshAction)
}
aboutToDisappear(): void {
this.unSubscribe()
}
build() {
if (this.groups.length > 0) {
Row() {
Row() {
Scroll(this.scroller) {
Row() {
ForEach(this.groups, (item: GroupInfo, index: number) => {
this.buildItem(item, index)
})
}
}
.scrollBar(BarState.Off)
.scrollable(ScrollDirection.Horizontal)
.width('auto')
}
.layoutWeight(1)
.justifyContent(FlexAlign.Start)
}
.width('100%')
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.Start)
.visibility(this.groups.length > 1 ? Visibility.Visible : Visibility.None)
}
}
@Builder
buildItem(item: GroupInfo, index: number) {
Column() {
Text(item.name)
.fontSize(15)
.fontColor(index === this.currentIndex ? $r('app.color.elements_text_primary_01') : $r('app.color.elements_text_primary_02'))
.backgroundColor(index === this.currentIndex ? $r('app.color.color_2e58ff_alp_10') :
$r('app.color.surface_layer3_background'))
.borderColor($r('app.color.elements_text_primary_01'))
.borderRadius(2)
.padding({
left: 10,
top: 5,
bottom: 5,
right: 10
})
}
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Center)
.height(35)
.padding({ left: index == 0 ? 0 : 5, right: 5 })
.onClick(() => {
if (this.currentIndex != index) {
this.selectIndex(index)
}
})
}
private refreshAction = () => {
this.initGroupInfo()
}
private subscribeLoginEvent() {
emitter.on({
eventId: EmitterConstants.USER_LOGIN
}, this.refreshAction)
emitter.on({
eventId: EmitterConstants.USER_LOGOUT
}, this.refreshAction);
}
private unSubscribeLoginEvent() {
emitter.off(EmitterConstants.USER_LOGIN, this.refreshAction);
emitter.off(EmitterConstants.USER_LOGOUT, this.refreshAction);
}
private onGroupChange = () => {
this.initGroupInfo()
}
private onUserLogin = () => {
if (this.isTemporaryUser()) {
SelectedIdOfWatchlistGroup.setDefaultId()
}
this.initGroupInfo()
}
private onPageStatusChange() {
if (this.onForeground) {
this.initGroupInfo()
emitter.on({
eventId: EmitterConstants.SELF_STOCK_GROUP_FILTER_CHANGE
}, this.onGroupChange)
emitter.on({
eventId: EmitterConstants.USER_LOGIN
}, this.onUserLogin)
} else {
emitter.off(EmitterConstants.SELF_STOCK_GROUP_FILTER_CHANGE, this.onGroupChange)
emitter.off(EmitterConstants.USER_LOGIN, this.onUserLogin)
}
}
private onGroupIndexChange() {
if (this.onSelectGroup) {
this.onSelectGroup(this.currentIndex)
}
if (this.onSelectGroupInfo && this.currentIndex < this.groups.length) {
this.onSelectGroupInfo(this.currentIndex, this.groups[this.currentIndex])
}
}
private onInitIndexChange() {
this.selectIndex(this.initIndex)
}
private initGroupInfo() {
let lastSelectIndex = this.currentIndex
let lastGroupInfo = lastSelectIndex < this.groups.length ? this.groups[lastSelectIndex]:undefined
if (!this.isTemporaryUser()) {
let groups = loadCustomGroups(true)
let defaultSelectGroupInfo = getSelfStock()
if (defaultSelectGroupInfo.length <= 0){
this.buildRecommendContractWhenSelfCodeNoData(lastSelectIndex,lastGroupInfo,groups)
return
}
groups.unshift({
id: FirstPageSelfCodeGroupView.GROUPID_DEFAULT,
name: '默认',
stocks: defaultSelectGroupInfo
})
this.groups = groups.slice(0, this.maxGroupCount)
this.initGroupIndexAndNotify(lastSelectIndex, lastGroupInfo)
} else {
let defaultStocks = getSelfStock()
if (defaultStocks.length > 0){
//临时账户有添加过自选股
let defaultGroupInfos: GroupInfo[] = []
defaultGroupInfos.push({
id: FirstPageSelfCodeGroupView.GROUPID_DEFAULT,
name: '默认',
stocks: getSelfStock()
})
this.groups = defaultGroupInfos
this.initGroupIndexAndNotify(lastSelectIndex,lastGroupInfo)
return
}
this.buildRecommendContractWhenSelfCodeNoData(lastSelectIndex,lastGroupInfo)
}
}
private buildRecommendContractWhenSelfCodeNoData(lastSelectIndex:number,lastGroupInfo?:GroupInfo,customGroupInfos?:GroupInfo[]){
RecommendContractManager.getInstance().getRecommendContract().then((result)=>{
if (result && result.length > 0) {
let groups:GroupInfo[] = []
let stocks:Array<Securities> = []
result.forEach((value:RecommendContract,index:number) =>{
if (value.stockInfo && HXUtils.isValidContractData(value.stockInfo.code) && HXUtils.isValidContractData(value.stockInfo.market)) {
let stock = new Securities(value.stockInfo.code,value.stockInfo.market)
stock.name = value.stockInfo.name??"--"
stocks.push(stock)
}
})
if (customGroupInfos) {
customGroupInfos.unshift({
id: FirstPageSelfCodeGroupView.GROUPID_DEFAULT,
name: '默认',
stocks: stocks
})
this.groups = customGroupInfos.slice(0, this.maxGroupCount)
this.initGroupIndexAndNotify(lastSelectIndex, lastGroupInfo)
}else {
groups.push({
id:FirstPageSelfCodeGroupView.GROUPID_RECOMMEND_CONTRACT,
name:"热门合约",
stocks:stocks
})
this.groups = groups
}
this.initGroupIndexAndNotify(lastSelectIndex,lastGroupInfo)
}else {
this.groups = []
this.initGroupIndexAndNotify(lastSelectIndex,lastGroupInfo)
}
})
}
private initGroupIndexAndNotify(lastSelectIndex: number, lastGroupInfo: GroupInfo | undefined) {
if (this.lastGroupId) {
this.groups.forEach((group, index) => {
if (group.id === this.lastGroupId) {
this.currentIndex = index
}
})
}
if (this.currentIndex >= this.groups.length) {
this.currentIndex = 0
}
if (lastSelectIndex == this.currentIndex) {
let newGroupInfo = this.currentIndex < this.groups.length ? this.groups[this.currentIndex] : undefined
if (JSON.stringify(lastGroupInfo) != JSON.stringify(newGroupInfo)) {
this.onGroupIndexChange()
}
}
}
private selectIndex(index: number) {
if (this.currentIndex != index && index < this.groups.length) {
//切换后保存当前的分组id
const groupInfo = this.groups[index]
this.lastGroupId = groupInfo.id
if (groupInfo.id != FirstPageSelfCodeGroupView.GROUPID_RECOMMEND_CONTRACT) {
//不进行热门合约的记录
PreferenceService.putVal(FirstPageSelfCodeGroupView.SP_FILE_NAME_FIRST_PAGE_SELF_CODE_FILE_NAME,
FirstPageSelfCodeGroupView.SP_KEY_FIRST_PAGE_SELF_CODE_LAST_GROUPID, this.lastGroupId, true)
}
}
this.currentIndex = index
if (this.totalWidth > 0) {
let offset = 0
let targetButtonWidth = this.itemWidthArray[index]
for (let i = 0; i < index; i++) {
let width = this.itemWidthArray[i]
if (width) {
offset += width
}
}
if (targetButtonWidth) {
offset -= (this.totalWidth - targetButtonWidth - this.OVERLAY_WIDTH) / 2
}
offset = Math.max(0, offset)
this.scroller.scrollTo({ xOffset: offset, yOffset: 0, animation: true })
}
}
//判断是否是临时账号
private isTemporaryUser(): boolean {
return HXUserService.getInstance()?.isTemporaryUser()
}
}
@@ -0,0 +1,90 @@
import { HXLog } from 'biz_common'
import { HQDataItemModel } from '../node/model/SelfSelectedStockModel'
const TAG = "FistPageTableRowBGView"
@Component
export struct FirstPageTableRowBGView {
@ObjectLink @Watch('onDataChange') hqItemDataModel: HQDataItemModel
@State bgOpacity: number = 0
@State bgColor: ResourceColor = Color.Transparent
@State startColor: string = "#ffffff"
@State endColor: string = "#ffffff"
private lastColor: ResourceColor = ""
private lastChangeTime = 0
onDataChange() {
HXLog.d(TAG, " FistPageTableRowBGView onDataChange this.hqItemDataModel:" +
JSON.stringify(this.hqItemDataModel))
if (!this.hqItemDataModel.priceChange) {
return
}
let color = this.getRiseBgColor(this.hqItemDataModel.riseFailFlag)
if (this.lastColor.toString().length > 0 && this.lastColor == color) {
return;
}
this.lastColor = color ?? ""
let now = Date.now()
if (typeof color === 'string' && color.length > 0 && now - this.lastChangeTime > 500) {
this.bgColor = color
this.startColor = this.getStartColor(this.hqItemDataModel.riseFailFlag)
this.endColor = this.getEndColor(this.hqItemDataModel.riseFailFlag)
this.bgOpacity = 1
this.lastChangeTime = now
animateTo({
duration: 500,
curve: Curve.EaseOut
}, () => {
this.bgOpacity = 0
})
}
}
build() {
this.buildBackgroundView()
}
@Builder
buildBackgroundView() {
Row()
.linearGradient({
direction: GradientDirection.Left,
colors: [[this.startColor, 0.0], [this.endColor, 1.0]]
})
.height('100%')
.width('100%')
.opacity(this.bgOpacity)
.onAppear(() => {
this.onDataChange()
})
.onDisAppear(() => {
this.bgOpacity = 0
})
}
private getRiseBgColor(riseFlag: number): string {
if (riseFlag == 0) {
return "#323232"
}
return riseFlag == 1 ? "#e93030" : "#009900"
}
private getStartColor(riseFlag: number): string {
if (riseFlag == 0) {
return "#FFFFFF"
}
return riseFlag == 1 ? "#00FF2436" : "#0007ab4b"
}
private getEndColor(riseFlag: number): string {
if (riseFlag == 0) {
return "#FFFFFF"
}
return riseFlag == 1 ? "#33ff2436" : "#3307ab4b"
}
}
+203
View File
@@ -0,0 +1,203 @@
import { CommonToast, GlobalContext } from 'biz_common';
import { getUserAvatarUrlByUid, LoginUtils } from 'biz_login';
import router from '@ohos.router';
import { TITLE_BAR_HEIGHT } from '../util/FirstPageConstant';
import { HXUserService } from 'biz_hxservice';
import { AppUtil } from '@pura/harmony-utils';
@Component
export default struct FirstPageTitleBar {
//用户userid,从根节点传入
@Consume userId: string;
//用户userName,从根节点传入
@Consume userName: string;
customToast: CustomDialogController = new CustomDialogController({
builder: CommonToast({ textContent: '体验版本功能暂未开放' }),
alignment: DialogAlignment.Center,
customStyle: true,
});
private showMessageTips: boolean = false
build() {
Stack({ alignContent: Alignment.TopStart }) {
Column() {
Row()
.width('100%')
.height(px2vp(GlobalContext.statusBarHeight))
.flexShrink(0)
Flex({ alignItems: ItemAlign.Center }) {
Column() {
Image(this.isTempUser(this.userName) ? $r('app.media.user_no_login') : getUserAvatarUrlByUid(this.userId))
.width(24)
.height(24)
.borderRadius(12)
.flexShrink(0)
.alt($r("app.media.user_no_login"))
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.height('100%')
.width(24)
.onClick(() => {
LoginUtils.gotoZone()
})
this.searchBar()
this.agentIcon()
this.messageIcon()
}
.padding({ left: 16, right: 16 })
.height(TITLE_BAR_HEIGHT)
.width('100%')
}
.width('100%')
.zIndex(1)
Column() {
}
.backgroundColor($r('app.color.surface_layer1_foreground'))
.width('100%')
.height(px2vp(GlobalContext.statusBarHeight) + TITLE_BAR_HEIGHT)
}
}
@Builder
messageIcon() {
Stack({ alignContent: Alignment.TopEnd }) {
Image($r("app.media.icon_mail_24"))
.fillColor($r('app.color.elements_icon_primary_02'))
.width('100%')
.height('100%');
Circle()
.width(3)
.height(3)
.stroke(Color.Red)
.fill(Color.Red)
.margin(3)
.visibility(this.showMessageTips ? Visibility.Visible : Visibility.None);
}.width(24).height(24).onClick(() => {
this.onMessageClick()
})
}
@Builder
agentIcon() {
Stack({ alignContent: Alignment.TopEnd }) {
Image($r("app.media.icon_bot_24"))
.fillColor($r('app.color.elements_icon_primary_02'))
.width('100%')
.height('100%');
Circle()
.width(3)
.height(3)
.stroke(Color.Red)
.fill(Color.Red)
.margin({ left: 3})
.visibility(this.showMessageTips ? Visibility.Visible : Visibility.None);
}.width(24).height(24).margin({ right: 10 }).onClick(() => {
this.onAgentClick()
})
}
@Builder
searchBar() {
Row() {
Image($r('app.media.icon_search_24'))
.width(18)
.height(18)
.fillColor($r('app.color.elements_icon_tertiary'))
Text("请输入代码/名称/简称")
.fontSize(14)
.fontColor($r('app.color.elements_text_quaternary'))
.margin({ left: 4 })
}
.alignItems(VerticalAlign.Center)
.margin({
top: 8,
bottom: 8,
left: 14,
right: 14
})
.padding({ left: 8 })
.height(28)
.layoutWeight(1)
.backgroundColor($r('app.color.elements_others_bg_layer1'))
.borderRadius(25)
.flexGrow(1)
.onClick(() => {
router.pushUrl({
url: 'pages/SearchPage' // 目标url
}, router.RouterMode.Standard, (err) => {
if (err) {
console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
return;
}
console.info('Invoke pushUrl succeeded.');
});
})
}
private isTempUser(userName: string): boolean {
return userName.length > 0 && userName.startsWith("mt_");
}
private onMessageClick() {
if (this.autoLogin()) {
return
}
router.pushUrl({
url: 'pages/WebViewComponentPage', params: {
url: 'https://fupage.10jqka.com.cn/projects/m/message/index.html',
title: '消息中心',
updateH5Title: '0'
}
},
router.RouterMode.Single, (err) => {
if (err) {
console.error(`go to message page failed , cause : ${err.message}`)
}
})
}
private onAgentClick() {
if (this.autoLogin()) {
return
}
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
}
},
router.RouterMode.Single, (err) => {
if (err) {
console.error(`go to message page failed , cause : ${err.message}`)
}
})
}
private autoLogin(): boolean {
if (!HXUserService.getInstance().isLoggedIn()) {
const topRoutePath = router.getState().path
router.pushUrl({
url: 'pages/login/LoginPage',
params: {
flexSwitchFlag: true,
sourcePage: `${topRoutePath}${router.getState().name}`
}
})
return true;
}
return false;
}
}
@@ -0,0 +1,127 @@
import { ScreenUtils } from '@b2b/hxutil'
import { ColorUtils } from '@b2c/lib_baseui'
import { TextMetricsMeasurer } from '@b2b/hq_table'
import { HQDataItemModel } from '../node/model/SelfSelectedStockModel'
import { FirstPageTableRowBGView } from './FirstPageTableRowBGView'
@Component
export struct FistPageSelfCodeGridItemView {
@ObjectLink hqItemData: HQDataItemModel
jumpToQuoteCallBack?: (tockModel: HQDataItemModel) => void
jumpToMoreCallBack?:()=>void
build() {
this.buildStockItem(this.hqItemData)
}
@Builder
buildStockItem(stockModel: HQDataItemModel) {
if (!stockModel.showAddImage) {
Column() {
Text(stockModel.name)
.minFontSize(10)
.maxFontSize(this.getAvailableFontSize(stockModel.name))
.fontColor($r('app.color.elements_text_primary_02'))
.textOverflow({ overflow: TextOverflow.Ellipsis })
.maxLines(1)
.margin({ bottom: 4 })
.width('100%')
Column(){
Stack(){
FirstPageTableRowBGView({
hqItemDataModel: stockModel
})
Text(stockModel.price.toString())
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor(ColorUtils.resourceColorToRGBHex(stockModel.riseFailColor))
.margin({ bottom: 2 })
}.alignContent(Alignment.Start)
}.width('100%')
.alignItems(HorizontalAlign.Start)
.height(25)
Row() {
Text(stockModel.priceRise)
.fontSize(11)
.fontColor(ColorUtils.resourceColorToRGBHex(stockModel.riseFailColor))
.margin({ right: 4 })
Text(`${stockModel.riseFail}`)
.fontSize(11)
.fontColor(ColorUtils.resourceColorToRGBHex(stockModel.riseFailColor))
}
}
.border({
width: { right: 1 }, // 只画右边框
color: $r('app.color.elements_others_divider_primary'),
style: BorderStyle.Solid
})
.onClick(() => {
this.jumpToQuote(stockModel)
})
.width('100%')
.height('100%')
.backgroundColor(Color.Transparent)
.padding(10)
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Center)
} else {
this.buildMoreItem()
}
}
jumpToQuote(stockModel: HQDataItemModel) {
if (this.jumpToQuoteCallBack) {
this.jumpToQuoteCallBack(stockModel)
}
}
@Builder
buildMoreItem() {
Column() {
Column(){
Image($r('app.media.icon_add_24'))
.fillColor($r('app.color.elements_icon_secondary'))
.width(20)
.height(20)
} .width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.border({
width: 1, // 只画右边框
color: $r('app.color.elements_others_border_base'),
style: BorderStyle.Dashed,
dashGap:{value:3,unit:1},
radius:4
})
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.surface_layer1_foreground'))
.padding(8)
.borderRadius(4)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.jumpToMore()
})
}
jumpToMore() {
if (this.jumpToMoreCallBack) {
this.jumpToMoreCallBack()
}
}
private getAvailableFontSize(name: string): number {
return TextMetricsMeasurer.getInstance().matchFontSize(name, (ScreenUtils.getScreenWidth(true) - 20) / 3 - 22, 14)
}
}
+14
View File
@@ -0,0 +1,14 @@
import { GlobalContext } from 'biz_common';
@Component
export struct StatusBar {
@Prop bgColor:string | ResourceColor = $r('app.color.surface_layer1_foreground')
build(){
Row()
.width('100%')
.height(px2vp(GlobalContext.statusBarHeight))
.backgroundColor(this.bgColor)
.flexShrink(0)
}
}
+125
View File
@@ -0,0 +1,125 @@
import { router } from '@kit.ArkUI'
import { StatusBar } from './StatusBar'
@Component
export struct TitleBar {
showStatusBar: boolean = false
@Prop statusBarBgColor:string
@Prop title: string
titleColor: ResourceColor = $r('app.color.elements_text_primary_02')
bgColor: ResourceColor = $r('app.color.surface_layer1_foreground')
leftWidth: number = 0
rightWidth: number = 0
@State leftMarginWidth: number = 0
@State rightMarginWidth: number = 0
@State showRefresh: boolean = false
onRefresh?: (() => void) | null
@BuilderParam buildLeftView?: (() => void) | null
@BuilderParam buildRightView?: (() => void) | null
private calculateMiddleMargin() {
let space = Math.abs(this.leftWidth - this.rightWidth)
if (this.leftWidth > this.rightWidth) {
this.leftMarginWidth = 0
this.rightMarginWidth = space
} else if (this.leftWidth < this.rightWidth) {
this.leftMarginWidth = space
this.rightMarginWidth = 0
} else {
this.leftMarginWidth = 0
this.rightMarginWidth = 0
}
}
build() {
Column() {
if (this.showStatusBar) {
StatusBar({
bgColor: this.statusBarBgColor
})
}
Row() {
Row() {
if (this.buildLeftView) {
this.buildLeftView()
} else {
this.buildBackView()
}
}
.height('100%')
.onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
this.leftWidth = newValue.width as number
this.calculateMiddleMargin()
})
Text(this.title)
.width('100%')
.height('100%')
.fontSize(16)
.fontColor(this.titleColor)
.textAlign(TextAlign.Center)
.maxLines(1)
.textOverflow({overflow: TextOverflow.Ellipsis})
.layoutWeight(1)
.margin({left: this.leftMarginWidth, right: this.rightMarginWidth})
Row() {
if (this.buildLeftView) {
this.buildLeftView()
} else {
this.buildRefreshView()
}
}
.height('100%')
.onSizeChange((oldValue: SizeOptions, newValue: SizeOptions) => {
this.rightWidth = newValue.width as number
this.calculateMiddleMargin()
})
}
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
.width('100%')
.height(42)
.backgroundColor(this.bgColor)
}
.width('100%')
}
@Builder
buildBackView() {
Column() {
Image($r('app.media.icon_arrow_left_24'))
.fillColor($r('app.color.elements_icon_primary_02'))
.width(19)
.height(24)
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.width(40)
.height('100%')
.onClick(() => {
router.back()
})
}
@Builder
buildRefreshView() {
if (this.showRefresh) {
Column() {
Image($r('app.media.title_bar_refresh'))
.width(20)
.height(20)
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.width(40)
.height('100%')
.onClick(() => {
if (this.onRefresh) {
this.onRefresh()
}
})
}
}
}