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
+37
View File
@@ -0,0 +1,37 @@
type DatePattern = 'yyyyMMdd' | 'yyyy-MM-dd' | 'yyyyMMddHHmmss' | 'HHmmss' | 'MM月dd日' | 'MM-dd HH:mm'
export class DateUtils {
static formatDate(date: Date, pattern: DatePattern = 'yyyyMMdd'): string {
let year = ""
let month = ""
let day = ""
let hour = ""
let minute = ""
let second = ""
try {
year = date.getFullYear().toString();
month = ('0' + (date.getMonth() + 1)).slice(-2);
day = ('0' + date.getDate()).slice(-2);
hour = ('0' + date.getHours()).slice(-2);
minute = ('0' + date.getMinutes()).slice(-2);
second = ('0' + date.getSeconds()).slice(-2);
} catch (e) {
//日志获取失败
return ""
}
if (pattern == 'yyyy-MM-dd') {
return year + '-' + month + '-' + day
} else if (pattern == 'yyyyMMddHHmmss') {
return year + month + day + hour + minute + second
} else if (pattern == 'HHmmss') {
return hour + minute + second
} else if (pattern == 'MM月dd日') {
return month + '月' + day + '日'
} else if (pattern == 'MM-dd HH:mm') {
return month + '-' + day + ' ' + hour + ':' + minute
} else {
return year + month + day
}
}
}
+137
View File
@@ -0,0 +1,137 @@
import { GlobalContext, HXLog } from 'biz_common'
import fs from '@ohos.file.fs';
import { BusinessError } from '@kit.BasicServicesKit';
export class FileManager {
private static readonly TAG = 'CacheManager'
private static instance: FileManager
public static getInstance() {
if (!FileManager.instance) {
FileManager.instance = new FileManager()
}
return FileManager.instance
}
saveCacheSync(saveContent: string, cacheDirPath: string): boolean {
let file: fs.File | null = null
try {
let filePath = this.getCachePathSync(cacheDirPath)
if (filePath) {
file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC)
if (saveContent.length > 0) {
fs.writeSync(file.fd, saveContent)
return true
}
}
} catch (error) {
let e: BusinessError = error as BusinessError
HXLog.e(FileManager.TAG, `error code + ${e.code}`)
} finally {
if (file !== null) {
try {
fs.closeSync(file)
} catch (error) {
let e: BusinessError = error as BusinessError
HXLog.e(FileManager.TAG, `close file error code + ${e.code}`)
}
}
}
return false
}
loadCacheSync(cacheDirPath: string): string {
try {
let filePath = this.getCachePathSync(cacheDirPath)
if (filePath) {
let content = fs.readTextSync(filePath)
if (content) {
return content;
}
}
} catch (e) {
let error: BusinessError = e as BusinessError;
HXLog.e(FileManager.TAG, `error code ${error.code} and message ${error.message}`)
}
return ''
}
saveFilesSync(saveContent: string, cacheDirPath: string): boolean {
let file: fs.File | null = null
try {
let filePath = this.getFilesPathSync(cacheDirPath)
if (filePath) {
file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC)
if (saveContent.length > 0) {
fs.writeSync(file.fd, saveContent)
return true
}
}
} catch (error) {
let e: BusinessError = error as BusinessError
HXLog.e(FileManager.TAG, `error code + ${e.code}`)
} finally {
if (file !== null) {
try {
fs.closeSync(file)
} catch (error) {
let e: BusinessError = error as BusinessError
HXLog.e(FileManager.TAG, `close file error code + ${e.code}`)
}
}
}
return false
}
loadFilesSync(cacheDirPath: string): string {
try {
let filePath = this.getFilesPathSync(cacheDirPath)
if (filePath) {
let content = fs.readTextSync(filePath)
if (content) {
return content;
}
}
} catch (e) {
let error: BusinessError = e as BusinessError;
HXLog.e(FileManager.TAG, `error code ${error.code} and message ${error.message}`)
}
return ''
}
public getCachePathSync(cacheDirPath: string): string | null {
try {
let context = GlobalContext.get()
let index = cacheDirPath.lastIndexOf('/')
let dirPath = context.cacheDir + cacheDirPath.substring(0, index)
let savePath = context.cacheDir + cacheDirPath
let res = fs.accessSync(dirPath)
if (!res) {
fs.mkdirSync(dirPath, true)
}
return savePath
} catch (error) {
let e: BusinessError = error as BusinessError;
HXLog.e(FileManager.TAG, `error code + ${e.code}`)
return null
}
}
public getFilesPathSync(cacheDirPath: string): string | null {
try {
let context = GlobalContext.get()
let index = cacheDirPath.lastIndexOf('/')
let dirPath = context.filesDir + cacheDirPath.substring(0, index)
let savePath = context.filesDir + cacheDirPath
let res = fs.accessSync(dirPath)
if (!res) {
fs.mkdirSync(dirPath, true)
}
return savePath
} catch (error) {
let e: BusinessError = error as BusinessError;
HXLog.e(FileManager.TAG, `error code + ${e.code}`)
return null
}
}
}
+2
View File
@@ -0,0 +1,2 @@
export const TITLE_BAR_HEIGHT = 50
+36
View File
@@ -0,0 +1,36 @@
import { AppUtil } from "@pura/harmony-utils"
export class FirstPageDebugUtil {
/**
* 九宫格全部节点
*/
static isAllGridNodeUseTestUrl: boolean = false
/**
* 九宫格页面替换跳转页面
* m/margins/index.html => account-open/index.html
* community/m/home.html => 测试环境futures-test.10jqka.com.cn/community/m/home.html
*/
static isGridNodeAllPageChangeUrl: boolean = false
/**
* 市场排名推荐合约接口
*/
static isMarketRankingUseTestUrl: boolean = false
static getGridNodeUrl(isTest: boolean): string {
if (isTest) {
return AppUtil.getContext().resourceManager.getStringSync($r('app.string.all_gride_node_url_test'))
}
return AppUtil.getContext().resourceManager.getStringSync($r('app.string.all_gride_node_url'))
}
static getMarketRankingRecommendUrl(isTest: boolean): string {
if (isTest) {
return AppUtil.getContext().resourceManager
.getStringSync($r('app.string.market_ranking_recommend_url_test'))
}
return AppUtil.getContext().resourceManager
.getStringSync($r('app.string.market_ranking_recommend_url'))
}
}
+16
View File
@@ -0,0 +1,16 @@
import { AppConfigManager } from 'biz_common/src/main/ets/appconfig/AppConfigManager';
import { CookieManager } from 'biz_hxservice';
export function buildHeader() : Record<string, Object> {
let header : Record<string, Object> = {
'User-Agent': AppConfigManager.getInstance().getUA(),
'Content-Type': 'application/json',
'Connection': 'keep-alive',
}
// 接入Cookie,已登录用户可获取完整数据
const cookie = CookieManager.getInstance().getCookieSync()
if (cookie) {
header['Cookie'] = cookie
}
return header;
}
+71
View File
@@ -0,0 +1,71 @@
import { image } from "@kit.ImageKit";
import { http } from "@kit.NetworkKit";
import { HXLog } from "biz_common";
import { BusinessError } from "@kit.BasicServicesKit";
/**
* author : liuqingliang@myhexin.com
* time : created on 2026/6/1
* desc : 网络图片加载
*/
const TAG = 'ImageLoader'
export class ImageLoader {
static load(imgUrl: string, onLoadFailed: (errMsg: string) => void, onResourceReady: (img: image.PixelMap) => void) {
try {
const httpRequest = http.createHttp()
httpRequest.request(imgUrl).then(async (response) => {
if (response.responseCode === 200) {
const arrayBuffer = response.result as ArrayBuffer
const imageSource = image.createImageSource(arrayBuffer)
try {
let img = imageSource.createPixelMapSync()
onResourceReady(img)
} finally {
await imageSource.release()
}
} else {
onLoadFailed(`ImageLoader load request failed. responseCode:${response.responseCode}`)
}
}).catch((err: Error) => {
onLoadFailed(err.message)
HXLog.e(TAG, JSON.stringify(err))
}).finally(() => {
httpRequest.destroy()
})
} catch (e) {
const err = e as BusinessError
HXLog.e(TAG, `ImageLoader load failed. msg:${err.message}`)
onLoadFailed(err.message)
}
}
static async loadSync(imgUrl: string): Promise<image.PixelMap | null> {
let img: image.PixelMap | null = null
let imageSource: image.ImageSource | null = null
try {
const httpRequest = http.createHttp()
try {
const response: http.HttpResponse = await httpRequest.request(imgUrl)
if (response.responseCode === 200) {
const arrayBuffer = response.result as ArrayBuffer
imageSource = image.createImageSource(arrayBuffer)
img = imageSource.createPixelMapSync()
}
} catch (e) {
const err = e as BusinessError
HXLog.e(TAG, `ImageLoader load failed. msg:${err.message}`)
} finally {
httpRequest.destroy()
}
} catch (e) {
const err = e as BusinessError
HXLog.e(TAG, `ImageLoader load failed. msg:${err.message}`)
} finally {
if (imageSource) {
imageSource.release()
}
}
return img
}
}
+8
View File
@@ -0,0 +1,8 @@
/**
* author:zhangshan2
* created on 2024/11/22
* desc:处理资源工具类
*/
export function getResourceString(context: Context, resource: Resource): string {
return context.resourceManager.getStringSync(resource)
}
+36
View File
@@ -0,0 +1,36 @@
import { HXLog, jumpToWebPage } from 'biz_common'
import { StringUtils } from 'hxutil'
import { landingPageRouteParam } from '../route'
import { promptAction } from '@kit.ArkUI';
import { call } from '@kit.TelephonyKit';
import { BusinessError } from '@kit.BasicServicesKit';
export class RouterUtil {
static jumpPage(url: string | null, fullscreen?: boolean) {
if (url && StringUtils.isNotEmpty(url)) {
if (url.startsWith('client.html')) {
let protocolItems = url.split('^')
for (let index = 0; index < protocolItems.length; index++) {
const protocolItem = protocolItems[index]
let urlKey = 'url'
if (protocolItem.startsWith(urlKey)) {
let jumpUrl = protocolItem.substring(urlKey.length + 1)
if (jumpUrl.startsWith('http://') || jumpUrl.startsWith('https://')) {
jumpToWebPage(landingPageRouteParam(url, jumpUrl, fullscreen))
return
}
}
}
promptAction.showToast({ message: '鸿蒙暂不支持' })
} else if (url.startsWith('tel://')) {
call.makeCall(url).then(() => {
HXLog.i("Telephony Service", "makeCallSuccess");
}).catch((err: BusinessError) => {
HXLog.i("Telephony Service", "makeCallFailed with message " + err.message)
});
} else {
jumpToWebPage(landingPageRouteParam(url, url, fullscreen))
}
}
}
}