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,109 @@
|
||||
/**
|
||||
* getAllCards 接口响应
|
||||
*/
|
||||
export interface AllCardsResponse {
|
||||
code: number
|
||||
msg: string
|
||||
data: AllCardsFloor[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 楼层数据
|
||||
*/
|
||||
export interface AllCardsFloor {
|
||||
id: number
|
||||
key: string
|
||||
title: string
|
||||
card_list: AllCardsCard[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 卡片配置
|
||||
*/
|
||||
export interface AllCardsCard {
|
||||
card_id: number
|
||||
card_key: string
|
||||
render_key: string
|
||||
card_title: CardTitle
|
||||
card_url: CardUrl
|
||||
mod_data: ModDataItem[]
|
||||
bury_point: string
|
||||
explain_title?: string
|
||||
explain_message?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 卡片标题
|
||||
*/
|
||||
export interface CardTitle {
|
||||
value: string
|
||||
type: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 卡片跳转链接
|
||||
*/
|
||||
export interface CardUrl {
|
||||
ios: string
|
||||
android: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据请求配置
|
||||
*/
|
||||
export interface ModDataItem {
|
||||
url: string
|
||||
param_list: string[]
|
||||
req_desc: string
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 诊断接口响应
|
||||
*/
|
||||
export interface AiDiagnosisResponse {
|
||||
status_code: number
|
||||
status_msg: string
|
||||
data: AiDiagnosisData
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 诊断数据
|
||||
*/
|
||||
export class AiDiagnosisData {
|
||||
market: string = ''
|
||||
score: string = ''
|
||||
code: string = ''
|
||||
factor_list: FactorItem[] = []
|
||||
name: string = ''
|
||||
recommend: RecommendItem[] = []
|
||||
message: string = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 因子项
|
||||
*/
|
||||
export class FactorItem {
|
||||
score: string = ''
|
||||
judge: string = ''
|
||||
factor: string = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 推荐合约项
|
||||
*/
|
||||
export class RecommendItem {
|
||||
market: string = ''
|
||||
score: string = ''
|
||||
code: string = ''
|
||||
name: string = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 推荐合约(用于点击跳转)
|
||||
*/
|
||||
export interface RecommendContract {
|
||||
name: string
|
||||
score: string
|
||||
code: string
|
||||
market: string
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* AI 看涨跌接口响应
|
||||
*/
|
||||
export interface AiRiseFallResponse {
|
||||
code: number
|
||||
msg: string
|
||||
data: AiRiseFallData
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 看涨跌数据
|
||||
*/
|
||||
export interface AiRiseFallData {
|
||||
date: string // 日期 "2026-06-01"
|
||||
last_trade_date_accuracy: string // 昨日正确率 "66.7"
|
||||
last_twenty_trade_date_accuracy: string // 近20日正确率 "66.4"
|
||||
rise: RiseFallListData // 看涨列表
|
||||
fall: RiseFallListData // 看跌列表
|
||||
}
|
||||
|
||||
/**
|
||||
* 看涨/看跌列表数据
|
||||
*/
|
||||
export interface RiseFallListData {
|
||||
count: number
|
||||
data: RiseFallItem[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 看涨/看跌项
|
||||
*/
|
||||
export interface RiseFallItem {
|
||||
variety: string // 品种代码 "SA"
|
||||
name: string // 合约名称 "纯碱2609"
|
||||
contract: string // 合约代码 "SA2609"
|
||||
market: string // 市场代码 "67"
|
||||
forecast: number // 预测方向 0=看涨, 1=看跌
|
||||
accuracy: string // 品种正确率 "80.0"
|
||||
show_quote: number // 是否显示行情 1=显示
|
||||
}
|
||||
|
||||
/**
|
||||
* 合约行情数据(用于行情订阅)
|
||||
*/
|
||||
export interface ContractHqData {
|
||||
contract: string
|
||||
market: string
|
||||
name: string
|
||||
priceChg: number | null // 涨跌幅,null 表示待开盘
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
export type BannerNodeModel = {
|
||||
id: number,
|
||||
title: string
|
||||
imgUrl: string
|
||||
jumpUrl: string
|
||||
startTime: number
|
||||
endTime: number
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 商品期权卡片数据模型
|
||||
*
|
||||
* Author: YS
|
||||
* Date: 2026-06-03
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tab 配置项
|
||||
*/
|
||||
export interface CommodityOptionsTab {
|
||||
label: ResourceStr // Tab 标签
|
||||
api: string // API 标识
|
||||
stat: string // 统计标识
|
||||
defaultMsg: ResourceStr // 空数据提示信息
|
||||
index: number // Tab 索引
|
||||
}
|
||||
|
||||
/**
|
||||
* Tab 配置
|
||||
*/
|
||||
export const COMMODITY_OPTIONS_TABS: CommodityOptionsTab[] = [
|
||||
{
|
||||
label: $r('app.string.tab_hot'),
|
||||
api: 'rise_percent',
|
||||
stat: 'hot',
|
||||
defaultMsg: $r('app.string.commodity_options_hot_default_msg'),
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
label: $r('app.string.tab_near_expired'),
|
||||
api: 'near_expired',
|
||||
stat: 'impend',
|
||||
defaultMsg: $r('app.string.commodity_options_near_expired_default_msg'),
|
||||
index: 1,
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* 推荐期权合约项
|
||||
*/
|
||||
export class CommodityOptionsContract {
|
||||
contract_code: string = '' // 合约代码
|
||||
contract_name: string = '' // 合约名称
|
||||
market: string = '' // 市场代码
|
||||
expired_date: string = '' // 到期日(格式:20250628)
|
||||
}
|
||||
|
||||
/**
|
||||
* API 响应
|
||||
*/
|
||||
export interface CommodityOptionsResponse {
|
||||
code: number
|
||||
msg: string
|
||||
data: CommodityOptionsContract[]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export class CustomEntryListNodeModel {
|
||||
id: number = -1
|
||||
title: string = ''
|
||||
imgUrl: string | Resource = ''
|
||||
jumpUrl: string = ''
|
||||
position: number = -1
|
||||
startVersion: number = 0
|
||||
endVersion: number = 0
|
||||
buriedPoint: string = ''
|
||||
buriedPointSource: string = ''
|
||||
gridTag: string = ''
|
||||
gridTagEffect: string = ''
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
export type EntryListNodeModel = {
|
||||
id: number,
|
||||
title: string,
|
||||
imgUrl: string,
|
||||
jumpUrl: string,
|
||||
startVersion: string,
|
||||
endVersion: string,
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
buriedPoint: string,
|
||||
buriedPointSource: string,
|
||||
gridTag: string
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 热点排行数据模型
|
||||
* @author YS
|
||||
* @date 2026-06-03
|
||||
*/
|
||||
|
||||
/**
|
||||
* 热点排行数据项
|
||||
* API 返回字段: title, url, tag, color, sorts, hot_value
|
||||
*/
|
||||
export interface HotListItemModel {
|
||||
url: string // 跳转地址
|
||||
title: string // 资讯标题
|
||||
tag: string | null // 标签名称(可能为 null)
|
||||
color: string | null // 颜色名称("red" | "orange",可能为 null)
|
||||
sorts: number // 排序值
|
||||
hot_value: string // 热度值
|
||||
}
|
||||
|
||||
/**
|
||||
* 热点排行接口返回结果
|
||||
*/
|
||||
export interface HotListResultModel {
|
||||
code: number // 接口返回码
|
||||
msg: string // 返回消息
|
||||
data: HotListItemModel[] // 热点数据列表
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色名称到资源ID的映射
|
||||
*/
|
||||
export const COLOR_NAME_MAP: Record<string, Resource> = {
|
||||
'purple': $r('app.color.hotlist_purple'),
|
||||
'blue': $r('app.color.hotlist_blue'),
|
||||
'acidblue': $r('app.color.hotlist_acid_blue'),
|
||||
'green': $r('app.color.hotlist_green'),
|
||||
'orange': $r('app.color.hotlist_orange'),
|
||||
'red': $r('app.color.hotlist_red'),
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认颜色资源
|
||||
*/
|
||||
const DEFAULT_COLOR: Resource = $r('app.color.hotlist_blue')
|
||||
|
||||
/**
|
||||
* 根据颜色名称获取颜色值
|
||||
*/
|
||||
export function getColorValue(colorName: string | null): ResourceStr {
|
||||
if (!colorName) {
|
||||
return DEFAULT_COLOR
|
||||
}
|
||||
const color = COLOR_NAME_MAP[colorName]
|
||||
return color ?? DEFAULT_COLOR
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
export type HotNewsNodeModelResult = {
|
||||
code: string,
|
||||
mes: string,
|
||||
data: HotNewsNodeModel[]
|
||||
}
|
||||
|
||||
export type HotNewsNodeModel = {
|
||||
seq: string, // 资讯唯一标识
|
||||
title: string, // 资讯标题
|
||||
source: string, // 来源
|
||||
url: string, // 资讯跳转地址
|
||||
img_url: string, // 图片地址
|
||||
create_time: string, // 资讯发布时间
|
||||
copyright: string, // 资讯版权
|
||||
type: string, // 资讯分类
|
||||
tags: HotNewsTagNodeModel[], // 资讯标签
|
||||
}
|
||||
|
||||
export type HotNewsTagNodeModel = {
|
||||
seq: string, // 资讯唯一标识
|
||||
name: string, // 标签名称
|
||||
weight: string, // 资讯权重
|
||||
code: string, // 品种代码
|
||||
market: string, // 市场id
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { BaseAd, FatigueDegree } from "@b2c-f/fuhm-ads"
|
||||
|
||||
/**
|
||||
* author : liuqingliang@myhexin.com
|
||||
* time : created on 2026/2/27
|
||||
* desc : 弹窗广告数据模型
|
||||
*/
|
||||
export class PopupAdMode {
|
||||
adid: string = ''
|
||||
startTime: number = 0
|
||||
endTime: number = 0
|
||||
imgUrl: string | null = null
|
||||
jumpUrl: string | null = null
|
||||
isOpenInnerWebView: boolean = false
|
||||
frequency: AdFrequency = AdFrequency.FREQUENCY_EVERY_LOGIN
|
||||
position: number = 0
|
||||
/**
|
||||
* 上次显示时间
|
||||
*/
|
||||
showTime: number = 0
|
||||
/**
|
||||
* 上次是否显示过
|
||||
*/
|
||||
hasShow: boolean = false
|
||||
fatigueDegree: FatigueDegree | null = null
|
||||
|
||||
toString(): string {
|
||||
return `PopupAdMode (adid='${this.adid}', frequency=${this.frequency}, position=${this.position}, hasShow=${this.hasShow}, fatigueDegree=${this.fatigueDegree})`
|
||||
}
|
||||
|
||||
toBaseAd() {
|
||||
let newAd = new BaseAd()
|
||||
newAd.adId = this.adid
|
||||
newAd.startTime = this.startTime
|
||||
newAd.endTime = this.endTime
|
||||
newAd.imgUrl = this.imgUrl ?? ""
|
||||
newAd.jumpUrl = this.jumpUrl ?? ""
|
||||
newAd.isOpenInnerWebView = this.isOpenInnerWebView
|
||||
newAd.fatigueDegree = this.fatigueDegree
|
||||
newAd.position = this.position
|
||||
return newAd
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 首页弹窗广告显示频率
|
||||
*/
|
||||
export enum AdFrequency {
|
||||
/**
|
||||
* 有效期间内,每次登录都显示
|
||||
*/
|
||||
FREQUENCY_EVERY_LOGIN = 0,
|
||||
/**
|
||||
* 有效期间内,每天显示一次
|
||||
*/
|
||||
FREQUENCY_EVERY_DAY = 1,
|
||||
/**
|
||||
* 有效期间内,只显示一次
|
||||
*/
|
||||
FREQUENCY_ONLY_ONCE = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* 首页cpyyy适配器
|
||||
* */
|
||||
export function adapterForIndexPopupActivity(newAdModels: BaseAd[]) {
|
||||
let oldAdModels: PopupAdMode[] = []
|
||||
newAdModels.forEach((it) => {
|
||||
let oldPopupAdMode = new PopupAdMode()
|
||||
oldPopupAdMode.adid = it.adId ?? ""
|
||||
oldPopupAdMode.startTime = it.startTime
|
||||
oldPopupAdMode.endTime = it.endTime
|
||||
oldPopupAdMode.imgUrl = it.imgUrl
|
||||
oldPopupAdMode.jumpUrl = it.jumpUrl
|
||||
oldPopupAdMode.isOpenInnerWebView = it.isOpenInnerWebView
|
||||
oldPopupAdMode.frequency =
|
||||
it.fatigueDegree?.isDailyShowOnce == 1 ? AdFrequency.FREQUENCY_EVERY_DAY : AdFrequency.FREQUENCY_EVERY_LOGIN
|
||||
oldPopupAdMode.fatigueDegree = it.fatigueDegree
|
||||
oldPopupAdMode.position = it.position
|
||||
oldAdModels.push(oldPopupAdMode)
|
||||
})
|
||||
return oldAdModels
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import { HXUtils, StockMarketUtil } from "@b2c/lib_baseui";
|
||||
import { NumberUtils } from "hxutil";
|
||||
|
||||
export interface SelfSelectedStockModel {
|
||||
id: string;
|
||||
stockCode: string;
|
||||
stockName: string;
|
||||
currentPrice: number;
|
||||
changeAmount: number;
|
||||
changePercent: number;
|
||||
updateTime?: number;
|
||||
}
|
||||
|
||||
export interface SelfSelectedStockModelMock {
|
||||
code: string;
|
||||
name: string;
|
||||
price: number;
|
||||
change: number;
|
||||
percent: number;
|
||||
}
|
||||
|
||||
export interface StockGroupModel {
|
||||
id: string;
|
||||
groupName: string;
|
||||
stocks: SelfSelectedStockModel[];
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
|
||||
const HQ_DATA_INVALID = "--"
|
||||
|
||||
@Observed
|
||||
export class SwiperHqDataItemModel extends Array<GridHqDataItemModel>{}
|
||||
|
||||
@Observed
|
||||
export class GridHqDataItemModel extends Array<HQDataItemModel>{}
|
||||
|
||||
/**
|
||||
* 行情自定义数据结构
|
||||
*/
|
||||
@Observed
|
||||
export class HQDataItemModel{
|
||||
name:string = HQ_DATA_INVALID
|
||||
code:string = HQ_DATA_INVALID
|
||||
showCode:string = HQ_DATA_INVALID
|
||||
market:string = "0"
|
||||
price:string = HQ_DATA_INVALID
|
||||
riseFail:string = HQ_DATA_INVALID //涨跌幅
|
||||
priceRise:string = HQ_DATA_INVALID
|
||||
nameColor:ResourceColor = 0
|
||||
priceColor:ResourceColor = 0
|
||||
riseFailColor:ResourceColor = 0
|
||||
priceRiseColor:ResourceColor = 0
|
||||
showAddImage:boolean = false
|
||||
/**
|
||||
* -1:跌,1:涨,0:不变
|
||||
*/
|
||||
riseFailFlag:number = 0
|
||||
|
||||
priceChange:boolean = false
|
||||
|
||||
|
||||
|
||||
constructor(stockCode?:string,stockMarket?:string,stockName?:string) {
|
||||
this.code = stockCode??HQ_DATA_INVALID
|
||||
this.market = stockMarket??HQ_DATA_INVALID
|
||||
this.name = stockName??HQ_DATA_INVALID
|
||||
this.showCode = stockCode??HQ_DATA_INVALID
|
||||
}
|
||||
|
||||
copyFrom(dataItemModel:HQDataItemModel){
|
||||
this.name = dataItemModel.name
|
||||
const market = dataItemModel.market
|
||||
const showCodeTemp = dataItemModel.showCode
|
||||
if ((StockMarketUtil.isOption(market+"") || StockMarketUtil.isFutureTL(market+"")) && HXUtils.isValidContractData(showCodeTemp+"")) {
|
||||
this.showCode = showCodeTemp
|
||||
}else{
|
||||
this.showCode = this.code
|
||||
}
|
||||
|
||||
this.riseFailFlag = this.getRiseFailFlag(dataItemModel.price,this.price)
|
||||
this.priceChange = this.riseFailFlag != 0
|
||||
this.price = dataItemModel.price
|
||||
this.riseFail = dataItemModel.riseFail
|
||||
this.priceRise = dataItemModel.priceRise
|
||||
this.riseFailColor = dataItemModel.riseFailColor
|
||||
this.riseFailColor = dataItemModel.riseFailColor
|
||||
}
|
||||
|
||||
getMapKey():string{
|
||||
return this.code+"_"+this.market
|
||||
}
|
||||
|
||||
|
||||
|
||||
private getRiseFailFlag(newPrice:string,lastPrice:string){
|
||||
let ret = 0
|
||||
if (newPrice == lastPrice){
|
||||
return ret
|
||||
}
|
||||
if (NumberUtils.isNumeric(newPrice) && NumberUtils.isNumeric(lastPrice)) {
|
||||
const newPriceNumber = parseFloat(newPrice)
|
||||
const lastPriceNumber = parseFloat(lastPrice)
|
||||
if (newPriceNumber == lastPriceNumber) {
|
||||
ret = 0
|
||||
}else {
|
||||
ret = newPriceNumber > lastPriceNumber ? 1:-1
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
copy():HQDataItemModel{
|
||||
let copyDataItemModel = new HQDataItemModel(this.code,this.market,this.name)
|
||||
copyDataItemModel.showCode = this.showCode
|
||||
copyDataItemModel.price = this.price
|
||||
copyDataItemModel.riseFail = this.riseFail
|
||||
copyDataItemModel.priceRise = this.priceRise
|
||||
copyDataItemModel.nameColor = this.nameColor
|
||||
copyDataItemModel.riseFailColor = this.riseFailColor
|
||||
copyDataItemModel.priceRiseColor = this.priceRiseColor
|
||||
copyDataItemModel.showAddImage = this.showAddImage
|
||||
copyDataItemModel.priceChange = this.priceChange
|
||||
copyDataItemModel.riseFailFlag = this.riseFailFlag
|
||||
return copyDataItemModel
|
||||
}
|
||||
}
|
||||
|
||||
export class GridDataSource implements IDataSource{
|
||||
private dataArray:HQDataItemModel[] = []
|
||||
|
||||
constructor(data:HQDataItemModel[]) {
|
||||
this.dataArray = data
|
||||
}
|
||||
totalCount(): number {
|
||||
return this.dataArray.length
|
||||
}
|
||||
|
||||
getData(index: number): HQDataItemModel {
|
||||
return this.dataArray[index]
|
||||
}
|
||||
|
||||
registerDataChangeListener(listener: DataChangeListener): void {
|
||||
|
||||
}
|
||||
|
||||
unregisterDataChangeListener(listener: DataChangeListener): void {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user