f57a0c3ebb
Use a theme-qualified tab gradient start color in both AI Pick and market ranking so the fade no longer introduces a white tint in dark mode. Add light and dark resource variants, and wrap AI Pick strategy tags in a fixed-height centered container while preserving single-line ellipsis behavior.
724 lines
23 KiB
Plaintext
724 lines
23 KiB
Plaintext
import { emitter } from '@kit.BasicServicesKit';
|
|
import {
|
|
CardData,
|
|
MetricId,
|
|
PeriodRange,
|
|
RankItem, RecommendFuturesItem, TabMetric,
|
|
} from './MarketRankingModels';
|
|
import { MarketRankingDataFetcher } from './MarketRankingDataFetcher';
|
|
import {
|
|
cardDataToContracts,
|
|
contractsToPlaceholderCardData,
|
|
} from './MarketRankingDataMapper';
|
|
import { MarketRankingHqRequestClient } from '../node/clients/MarketRankingHqRequestClient';
|
|
import { formatGreatNumber, formatPercent, formatPrice, riseFallColor } from '../common/NumberFormat';
|
|
import { AllCardsCard } from '../node/model/AiDiagnosisNodeModel';
|
|
import { PERIOD_RANGES, TAB_METRICS } from './MarketRankingConstant';
|
|
import { enableDarkMode } from '@kernel/theme_manager';
|
|
import { EmitterConstants, GlobalContext, jumpToQuote } from 'biz_common';
|
|
import { RouterUtil } from '../util/RouterUtil';
|
|
|
|
const NORMAL_SIZE = 3;
|
|
const MAX_SIZE = 5;
|
|
const UNFOLDED_STORAGE_KEY = 'firstPageMarketRankingUnfolded';
|
|
|
|
PersistentStorage.persistProp<boolean>(UNFOLDED_STORAGE_KEY, false);
|
|
|
|
@Component
|
|
export struct MarketRankingNodeComponent {
|
|
// 外部状态
|
|
@Link @Watch('onRefreshTriggerChange') refreshTrigger: number;
|
|
@Consume('firstPageVisible') @Watch('onFirstPageVisibleChange') firstPageVisible: boolean = true;
|
|
@StorageProp(enableDarkMode) isDarkMode: boolean = false;
|
|
|
|
// 卡片业务数据
|
|
@State cardConfig: AllCardsCard | undefined = undefined;
|
|
@State cardData: CardData = { tableList: [] };
|
|
private contracts: RecommendFuturesItem[] = [];
|
|
private tabDataCache: Map<string, CardData> = new Map<string, CardData>();
|
|
// 区分加载中和暂无数据,与 HotListNodeComponent 的 dataReady 约定保持一致
|
|
@State dataReady: boolean = false;
|
|
|
|
// Tab 与展开状态
|
|
@State primaryIdx: number = 0;
|
|
@State secondaryIdx: number = 0;
|
|
@StorageLink(UNFOLDED_STORAGE_KEY) unfolded: boolean = false;
|
|
|
|
// Tab 滚动区域状态
|
|
@State tabViewportWidth: number = 0;
|
|
@State tabContentWidth: number = 0;
|
|
@State tabScrollOffset: number = 0;
|
|
|
|
// 弹层状态
|
|
@State showExplainSheet: boolean = false;
|
|
|
|
// 生命周期与异步请求状态
|
|
private isComponentActive: boolean = false;
|
|
private dataRequestVersion: number = 0;
|
|
|
|
// 行情订阅与事件状态
|
|
private hqUnsubscribe: (() => void) | undefined;
|
|
private hqSubscriptionVersion: number = 0;
|
|
private isTcpEventSubscribed: boolean = false;
|
|
private tcpReconnectListener = (): void => {
|
|
if (!this.canUseHq()) {
|
|
return;
|
|
}
|
|
setTimeout((): void => {
|
|
if (this.canUseHq()) {
|
|
this.restartCurrentSubscription();
|
|
}
|
|
});
|
|
};
|
|
|
|
// ==================== 组件生命周期 ====================
|
|
|
|
aboutToAppear(): void {
|
|
this.isComponentActive = true;
|
|
this.updateCardConfig();
|
|
if (this.firstPageVisible) {
|
|
this.subscribeTcpNetStatus();
|
|
}
|
|
this.updateCardData(this.primaryIdx, this.secondaryIdx);
|
|
}
|
|
|
|
aboutToDisappear(): void {
|
|
this.isComponentActive = false;
|
|
this.invalidateDataRequest();
|
|
this.stopSubscribeHq();
|
|
this.unSubscribeTcpNetStatus();
|
|
this.contracts = [];
|
|
}
|
|
|
|
// ==================== 配置与刷新 ====================
|
|
|
|
// 从首页统一卡片配置中读取市场排名配置。
|
|
private updateCardConfig(): void {
|
|
this.cardConfig = MarketRankingDataFetcher.getInstance().fetchCardConfig();
|
|
}
|
|
|
|
// refreshTrigger 变化时重新获取当前 Tab 的合约,并在请求完成后重建行情订阅。
|
|
onRefreshTriggerChange(): void {
|
|
if (!this.isComponentActive) {
|
|
return;
|
|
}
|
|
this.updateCardConfig();
|
|
this.updateCardData(this.primaryIdx, this.secondaryIdx);
|
|
}
|
|
|
|
// ==================== 可见性与 TCP 重连 ====================
|
|
|
|
// 页面不可见时停止行情;恢复可见后使用已有合约重建订阅。
|
|
onFirstPageVisibleChange(): void {
|
|
if (!this.isComponentActive) {
|
|
return;
|
|
}
|
|
if (!this.firstPageVisible) {
|
|
this.stopSubscribeHq();
|
|
this.unSubscribeTcpNetStatus();
|
|
return;
|
|
}
|
|
this.subscribeTcpNetStatus();
|
|
this.restartCurrentSubscription();
|
|
}
|
|
|
|
private subscribeTcpNetStatus(): void {
|
|
if (this.isTcpEventSubscribed || !this.canUseHq()) {
|
|
return;
|
|
}
|
|
this.isTcpEventSubscribed = true;
|
|
emitter.on({ eventId: EmitterConstants.NETWORK_TCP_RECONNECT }, this.tcpReconnectListener);
|
|
}
|
|
|
|
private unSubscribeTcpNetStatus(): void {
|
|
if (!this.isTcpEventSubscribed) {
|
|
return;
|
|
}
|
|
this.isTcpEventSubscribed = false;
|
|
emitter.off(EmitterConstants.NETWORK_TCP_RECONNECT, this.tcpReconnectListener);
|
|
}
|
|
|
|
// 收到 TCP 重连事件后,仅在页面可见时重新订阅行情。
|
|
private restartCurrentSubscription(): void {
|
|
if (!this.canUseHq()) {
|
|
return;
|
|
}
|
|
if (this.contracts.length > 0) {
|
|
this.subscribeHq(this.primaryIdx, this.secondaryIdx);
|
|
return;
|
|
}
|
|
if (this.dataReady) {
|
|
this.updateCardData(this.primaryIdx, this.secondaryIdx);
|
|
}
|
|
}
|
|
|
|
// ==================== Tab 交互 ====================
|
|
|
|
private selectPrimaryTab(index: number): void {
|
|
if (this.primaryIdx === index) {
|
|
return;
|
|
}
|
|
this.primaryIdx = index;
|
|
this.secondaryIdx = 0;
|
|
this.updateCardData(index, 0);
|
|
}
|
|
|
|
private selectSecondaryTab(index: number): void {
|
|
if (this.secondaryIdx === index) {
|
|
return;
|
|
}
|
|
this.secondaryIdx = index;
|
|
this.updateCardData(this.primaryIdx, index);
|
|
}
|
|
|
|
// ==================== 数据请求与行情订阅 ====================
|
|
|
|
// 先请求合约榜单填充列表,再根据当前 CardData 订阅行情。
|
|
private async updateCardData(primaryIdx: number, secondaryIdx: number): Promise<void> {
|
|
if (!this.isComponentActive) {
|
|
return;
|
|
}
|
|
const requestVersion: number = this.dataRequestVersion + 1;
|
|
this.dataRequestVersion = requestVersion;
|
|
const cacheKey: string = this.getTabCacheKey(primaryIdx, secondaryIdx);
|
|
const cachedData: CardData | undefined = this.tabDataCache.get(cacheKey);
|
|
this.stopSubscribeHq();
|
|
if (cachedData !== undefined) {
|
|
this.cardData = cachedData;
|
|
this.contracts = cardDataToContracts(cachedData);
|
|
this.dataReady = true;
|
|
if (this.firstPageVisible) {
|
|
this.subscribeHq(primaryIdx, secondaryIdx);
|
|
}
|
|
} else {
|
|
this.contracts = [];
|
|
this.cardData = { tableList: [] };
|
|
this.dataReady = false;
|
|
}
|
|
try {
|
|
const contracts = await MarketRankingDataFetcher
|
|
.getInstance()
|
|
.fetchRecommendFutures(primaryIdx, secondaryIdx);
|
|
if (!this.isCurrentDataRequest(requestVersion, primaryIdx, secondaryIdx)) {
|
|
return;
|
|
}
|
|
this.contracts = contracts;
|
|
if (cachedData === undefined) {
|
|
this.cardData = contractsToPlaceholderCardData(contracts);
|
|
}
|
|
this.dataReady = true;
|
|
this.tabDataCache.set(cacheKey, this.cardData);
|
|
if (this.firstPageVisible) {
|
|
this.subscribeHq(primaryIdx, secondaryIdx);
|
|
}
|
|
} catch {
|
|
if (!this.isCurrentDataRequest(requestVersion, primaryIdx, secondaryIdx)) {
|
|
return;
|
|
}
|
|
if (cachedData === undefined) {
|
|
this.contracts = [];
|
|
this.cardData = { tableList: [] };
|
|
}
|
|
this.dataReady = true;
|
|
}
|
|
}
|
|
|
|
// 使用 HTTP 保存的原始合约列表订阅行情,推送后整体更新 CardData。
|
|
private subscribeHq(primaryIdx: number, secondaryIdx: number): void {
|
|
this.stopSubscribeHq();
|
|
if (!this.canUseHq() || this.contracts.length === 0) {
|
|
return;
|
|
}
|
|
const subscriptionVersion: number = this.hqSubscriptionVersion;
|
|
|
|
this.hqUnsubscribe = MarketRankingHqRequestClient
|
|
.getInstance()
|
|
.subscribeHq(this.contracts, primaryIdx, secondaryIdx, (cardData: CardData): void => {
|
|
if (this.canUseHq() &&
|
|
subscriptionVersion === this.hqSubscriptionVersion &&
|
|
primaryIdx === this.primaryIdx &&
|
|
secondaryIdx === this.secondaryIdx) {
|
|
this.cardData = cardData;
|
|
this.tabDataCache.set(this.getTabCacheKey(primaryIdx, secondaryIdx), cardData);
|
|
}
|
|
});
|
|
}
|
|
|
|
private stopSubscribeHq(): void {
|
|
this.hqSubscriptionVersion += 1;
|
|
if (this.hqUnsubscribe !== undefined) {
|
|
this.hqUnsubscribe();
|
|
this.hqUnsubscribe = undefined;
|
|
}
|
|
}
|
|
|
|
private canUseHq(): boolean {
|
|
return this.isComponentActive && this.firstPageVisible;
|
|
}
|
|
|
|
private invalidateDataRequest(): void {
|
|
this.dataRequestVersion += 1;
|
|
}
|
|
|
|
private isCurrentDataRequest(
|
|
requestVersion: number,
|
|
primaryIdx: number,
|
|
secondaryIdx: number,
|
|
): boolean {
|
|
return this.isComponentActive &&
|
|
requestVersion === this.dataRequestVersion &&
|
|
primaryIdx === this.primaryIdx &&
|
|
secondaryIdx === this.secondaryIdx;
|
|
}
|
|
|
|
private getTabCacheKey(primaryIdx: number, secondaryIdx: number): string {
|
|
const metric: TabMetric = TAB_METRICS[primaryIdx];
|
|
if (metric.hasChildren) {
|
|
return `${metric.id}_${PERIOD_RANGES[secondaryIdx].id}`;
|
|
}
|
|
return `${metric.id}`;
|
|
}
|
|
|
|
// ==================== 页面跳转 ====================
|
|
|
|
// 对齐 futures-homepage jumpToFenShi,跳转客户端行情分时页。
|
|
private jumpToDetail(item: RankItem): void {
|
|
const codeList: string =
|
|
this.contracts.map((contract: RecommendFuturesItem): string => contract.contract_code).join(',');
|
|
const marketIdList: string =
|
|
this.contracts.map((contract: RecommendFuturesItem): string => contract.market).join(',');
|
|
const nameList: string =
|
|
this.contracts.map((contract: RecommendFuturesItem): string => contract.contract_name).join(',');
|
|
jumpToQuote(item.code, item.market, codeList, marketIdList, nameList);
|
|
}
|
|
|
|
// 跳转到卡片配置中的标题地址。
|
|
private jumpToCardTitle(): void {
|
|
const jumpUrl: string =
|
|
this.cardConfig?.card_url.android || this.cardConfig?.card_url.ios || '';
|
|
if (jumpUrl === '') {
|
|
return;
|
|
}
|
|
RouterUtil.jumpPage(jumpUrl, true);
|
|
}
|
|
|
|
// ==================== UI 构建 ====================
|
|
|
|
build() {
|
|
Column() {
|
|
this.buildTitleBar()
|
|
this.buildTabBar()
|
|
|
|
if (this.cardData.tableList.length > 0) {
|
|
this.buildContent()
|
|
} else {
|
|
this.buildEmptyView()
|
|
}
|
|
}
|
|
.width('100%')
|
|
.backgroundColor($r('app.color.surface_layer1_foreground'))
|
|
.borderRadius(4)
|
|
.padding({ left: 10, right: 10, bottom: 10 })
|
|
.bindSheet(this.showExplainSheet, this.buildExplainSheetContent, {
|
|
height: SheetSize.FIT_CONTENT,
|
|
dragBar: true,
|
|
showClose: false,
|
|
maskColor: $r('app.color.mask_level2'),
|
|
onDisappear: () => {
|
|
this.showExplainSheet = false;
|
|
},
|
|
radius: {
|
|
topLeft: 10,
|
|
topRight: 10,
|
|
},
|
|
})
|
|
}
|
|
|
|
@Builder
|
|
buildTitleBar() {
|
|
Row() {
|
|
Row() {
|
|
Text(this.cardConfig?.card_title.value || $r('app.string.market_ranking_title'))
|
|
.fontColor($r('app.color.elements_text_primary_02'))
|
|
.fontSize(18)
|
|
.fontWeight(500)
|
|
.maxLines(1)
|
|
|
|
if ((this.cardConfig?.card_url.android ?? '') !== '' ||
|
|
(this.cardConfig?.card_url.ios ?? '') !== '') {
|
|
Image($r('app.media.icon_arrow_forward_20'))
|
|
.width(20)
|
|
.height(20)
|
|
.fillColor($r('app.color.elements_icon_primary_02'))
|
|
}
|
|
|
|
if ((this.cardConfig?.explain_message ?? '') !== '') {
|
|
Image($r('app.media.icon_info_24'))
|
|
.width(16)
|
|
.height(16)
|
|
.fillColor($r('app.color.elements_icon_tertiary'))
|
|
.margin({ left: 8 })
|
|
.onClick(() => {
|
|
this.showExplainSheet = true;
|
|
})
|
|
}
|
|
}
|
|
.alignItems(VerticalAlign.Center)
|
|
.height('100%')
|
|
.onClick(() => {
|
|
this.jumpToCardTitle();
|
|
})
|
|
|
|
// 展开/收起按钮,样式对齐 HotListNodeComponent 的展开/收起按钮
|
|
Row() {
|
|
Text(this.unfolded
|
|
? $r('app.string.hotlist_collapse')
|
|
: $r('app.string.hotlist_expand'))
|
|
.fontColor($r('app.color.elements_text_tertiary'))
|
|
.fontSize(12)
|
|
.height(16)
|
|
.width('100%')
|
|
.textAlign(TextAlign.Center)
|
|
}
|
|
.width(40)
|
|
.height(20)
|
|
.borderRadius(10)
|
|
.backgroundColor($r('app.color.elements_button_bg_disable_02'))
|
|
.margin({ left: 8 })
|
|
.onClick(() => {
|
|
this.toggleUnfolded();
|
|
})
|
|
|
|
Blank()
|
|
}
|
|
.width('100%')
|
|
.height(48)
|
|
}
|
|
|
|
@Builder
|
|
buildExplainSheetContent() {
|
|
Column() {
|
|
Stack({ alignContent: Alignment.End }) {
|
|
Text(this.cardConfig?.explain_title ||
|
|
this.cardConfig?.card_title.value ||
|
|
$r('app.string.market_ranking_title'))
|
|
.fontSize(18)
|
|
.fontWeight(500)
|
|
.textAlign(TextAlign.Center)
|
|
.width('100%')
|
|
|
|
Row() {
|
|
Image($r('app.media.icon_close_24'))
|
|
.width(24)
|
|
.height(24)
|
|
.fillColor($r('app.color.elements_icon_primary_02'))
|
|
.onClick(() => {
|
|
this.showExplainSheet = false;
|
|
})
|
|
}
|
|
.height('100%')
|
|
.justifyContent(FlexAlign.End)
|
|
}
|
|
.height(64)
|
|
.width('100%')
|
|
|
|
Scroll() {
|
|
Text((this.cardConfig?.explain_message ?? '').replace(new RegExp('\\\\n', 'g'), '\n'))
|
|
.width('100%')
|
|
.fontSize(14)
|
|
.lineHeight(22)
|
|
.fontColor($r('app.color.elements_text_primary_02'))
|
|
}
|
|
.width('100%')
|
|
.scrollBar(BarState.Off)
|
|
|
|
Button($r('app.string.card_explain_confirm'))
|
|
.type(ButtonType.Normal)
|
|
.backgroundColor($r('app.color.elements_button_bg_primary'))
|
|
.width('100%')
|
|
.height(44)
|
|
.borderRadius(4)
|
|
.margin({ top: 16 })
|
|
.onClick(() => {
|
|
this.showExplainSheet = false;
|
|
})
|
|
}
|
|
.width('100%')
|
|
.backgroundColor($r('app.color.surface_layer3_background'))
|
|
.padding({
|
|
bottom: px2vp(GlobalContext.navigationBarHeight),
|
|
left: 16,
|
|
right: 16,
|
|
})
|
|
}
|
|
|
|
@Builder
|
|
buildTabBar() {
|
|
Column() {
|
|
// 一级 Tab(胶囊按钮,可横向滚动;Tab 数量多于 CommodityOptions 等固定两栏卡片,
|
|
// 因此保留横向滚动 + 边缘渐隐,属于本卡片必要的交互差异,非样式不一致)。
|
|
Stack({ alignContent: Alignment.End }) {
|
|
Scroll() {
|
|
Row({ space: 8 }) {
|
|
ForEach(TAB_METRICS, (metric: TabMetric, index: number) => {
|
|
Text(metric.label)
|
|
.fontColor(this.primaryIdx === index
|
|
? $r('app.color.elements_text_primary_01')
|
|
: $r('app.color.elements_text_secondary'))
|
|
.fontSize(14)
|
|
.fontWeight(this.primaryIdx === index ? FontWeight.Medium : FontWeight.Normal)
|
|
.padding({ left: 8, right: 8 })
|
|
.height(30)
|
|
.constraintSize({ minWidth: 66 })
|
|
.textAlign(TextAlign.Center)
|
|
.backgroundColor(this.primaryIdx === index
|
|
? $r('app.color.elements_others_bg_primary')
|
|
: $r('app.color.elements_button_bg_tertiary'))
|
|
.borderRadius(4)
|
|
.onClick(() => {
|
|
this.selectPrimaryTab(index);
|
|
})
|
|
}, (metric: TabMetric) => metric.id)
|
|
}
|
|
.onAreaChange((oldValue: Area, newValue: Area) => {
|
|
this.tabContentWidth = Number(newValue.width);
|
|
})
|
|
}
|
|
.width('100%')
|
|
.height(30)
|
|
.padding({ right: 10 })
|
|
.scrollable(ScrollDirection.Horizontal)
|
|
.scrollBar(BarState.Off)
|
|
.align(Alignment.Start)
|
|
.onScroll((xOffset: number, _yOffset: number) => {
|
|
this.tabScrollOffset = xOffset;
|
|
})
|
|
if (this.showTabGradient()) {
|
|
Row()
|
|
.width(24)
|
|
.height(30)
|
|
.linearGradient({
|
|
angle: 90,
|
|
colors: [[$r('app.color.tab_gradient_start'), 0],
|
|
[$r('app.color.surface_layer1_foreground'), 1]],
|
|
})
|
|
.hitTestBehavior(HitTestMode.None)
|
|
}
|
|
}
|
|
.width('100%')
|
|
.height(30)
|
|
.onAreaChange((oldValue: Area, newValue: Area) => {
|
|
this.tabViewportWidth = Number(newValue.width);
|
|
})
|
|
|
|
// 二级 Tab(仅涨速/跌速出现,右对齐文字 + 分隔线)
|
|
if (TAB_METRICS[this.primaryIdx].hasChildren) {
|
|
Row() {
|
|
ForEach(PERIOD_RANGES, (period: PeriodRange, index: number) => {
|
|
if (index !== 0) {
|
|
Divider()
|
|
.vertical(true)
|
|
.height(10)
|
|
.color($r('app.color.elements_others_divider_primary'))
|
|
.margin({ left: 8, right: 8 })
|
|
}
|
|
Text(period.label)
|
|
.fontSize(12)
|
|
.fontWeight(this.secondaryIdx === index ? FontWeight.Medium : FontWeight.Normal)
|
|
.fontColor(this.secondaryIdx === index
|
|
? $r('app.color.elements_text_primary_02')
|
|
: $r('app.color.elements_text_tertiary'))
|
|
.padding({ top: 4, bottom: 4 })
|
|
.onClick(() => {
|
|
this.selectSecondaryTab(index);
|
|
})
|
|
}, (period: PeriodRange) => period.id)
|
|
}
|
|
.width('100%')
|
|
.justifyContent(FlexAlign.End)
|
|
.margin({ top: 8 })
|
|
}
|
|
}
|
|
.width('100%')
|
|
}
|
|
|
|
@Builder
|
|
buildEmptyView() {
|
|
Column() {
|
|
Text(this.dataReady ? $r('app.string.hotlist_no_data') : $r('app.string.hotlist_loading'))
|
|
.fontColor($r('app.color.elements_text_tertiary'))
|
|
.fontSize(14)
|
|
}
|
|
.width('100%')
|
|
.height(this.emptyHeight())
|
|
.alignItems(HorizontalAlign.Center)
|
|
.justifyContent(FlexAlign.Center)
|
|
}
|
|
|
|
@Builder
|
|
buildContent() {
|
|
Column() {
|
|
// 表头
|
|
Row({ space: 8 }) {
|
|
Text($r('app.string.commodity_options_table_header_name'))
|
|
.fontColor($r('app.color.elements_text_tertiary'))
|
|
.fontSize(14)
|
|
.width('35%')
|
|
Text($r('app.string.commodity_options_table_header_price'))
|
|
.fontColor($r('app.color.elements_text_tertiary'))
|
|
.fontSize(14)
|
|
.layoutWeight(1)
|
|
.textAlign(TextAlign.End)
|
|
Text(this.thirdColumnTitle())
|
|
.fontColor($r('app.color.elements_text_tertiary'))
|
|
.fontSize(14)
|
|
.layoutWeight(1)
|
|
.textAlign(TextAlign.End)
|
|
}
|
|
.height(40)
|
|
.width('100%')
|
|
|
|
// 表格列表
|
|
ForEach(
|
|
this.cardData.tableList.slice(0, this.displaySize()),
|
|
(item: RankItem, index: number) => {
|
|
this.buildRankRow(item)
|
|
},
|
|
// 行情更新时字段参与 key,确保整体替换 CardData 后列表行同步刷新。
|
|
(item: RankItem) =>
|
|
`${item.code}_${item.market}_${item.price ?? ''}_${item.price_chg ?? ''}_` +
|
|
`${item.chg_speed ?? ''}_${item.turnover ?? ''}_${item.incre_posit ?? ''}`,
|
|
)
|
|
}
|
|
.width('100%')
|
|
.height(this.emptyHeight())
|
|
.animation({ duration: 180, curve: Curve.EaseInOut })
|
|
}
|
|
|
|
@Builder
|
|
buildRankRow(item: RankItem) {
|
|
Row({ space: 8 }) {
|
|
Text(item.name)
|
|
.fontColor($r('app.color.elements_text_primary_02'))
|
|
.fontSize(16)
|
|
.maxFontSize(16)
|
|
.minFontSize(9)
|
|
.width('35%')
|
|
.maxLines(1)
|
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
Text(formatPrice(item.price))
|
|
.fontColor(riseFallColor(item.price_chg))
|
|
.fontSize(16)
|
|
.fontWeight(500)
|
|
.maxFontSize(16)
|
|
.minFontSize(9)
|
|
.layoutWeight(1)
|
|
.textAlign(TextAlign.End)
|
|
.maxLines(1)
|
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
Text(this.thirdColumnValue(item))
|
|
.fontColor(this.thirdColumnColor(item))
|
|
.fontSize(16)
|
|
.fontWeight(500)
|
|
.maxFontSize(16)
|
|
.minFontSize(9)
|
|
.layoutWeight(1)
|
|
.textAlign(TextAlign.End)
|
|
.maxLines(1)
|
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
}
|
|
.width('100%')
|
|
.height(45)
|
|
.alignItems(VerticalAlign.Center)
|
|
.border({ width: { top: 1 }, color: $r('app.color.elements_others_divider_primary') })
|
|
.onClick(() => {
|
|
this.jumpToDetail(item);
|
|
})
|
|
}
|
|
|
|
// ==================== 展示计算 ====================
|
|
|
|
private toggleUnfolded(): void {
|
|
this.unfolded = !this.unfolded;
|
|
}
|
|
|
|
private showTabGradient(): boolean {
|
|
const maxOffset = this.tabContentWidth - this.tabViewportWidth;
|
|
return maxOffset > 0 && this.tabScrollOffset < maxOffset;
|
|
}
|
|
|
|
private emptyHeight(): number {
|
|
const rowHeight = 45;
|
|
const headerHeight = 40;
|
|
return this.displaySize() * rowHeight + headerHeight;
|
|
}
|
|
|
|
private displaySize(): number {
|
|
return this.unfolded ? MAX_SIZE : NORMAL_SIZE;
|
|
}
|
|
|
|
// 第三列标题随 Tab 变化
|
|
private thirdColumnTitle(): ResourceStr {
|
|
const metricId = TAB_METRICS[this.primaryIdx].id;
|
|
if (metricId === MetricId.RISE || metricId === MetricId.FALL) {
|
|
return $r('app.string.market_ranking_change_rate');
|
|
}
|
|
if (metricId === MetricId.RISE_SPEED) {
|
|
switch (this.secondaryIdx) {
|
|
case 0:
|
|
return $r('app.string.market_ranking_1min_rise_speed');
|
|
case 1:
|
|
return $r('app.string.market_ranking_5min_rise_speed');
|
|
case 2:
|
|
return $r('app.string.market_ranking_10min_rise_speed');
|
|
default:
|
|
return $r('app.string.market_ranking_15min_rise_speed');
|
|
}
|
|
}
|
|
if (metricId === MetricId.FALL_SPEED) {
|
|
switch (this.secondaryIdx) {
|
|
case 0:
|
|
return $r('app.string.market_ranking_1min_fall_speed');
|
|
case 1:
|
|
return $r('app.string.market_ranking_5min_fall_speed');
|
|
case 2:
|
|
return $r('app.string.market_ranking_10min_fall_speed');
|
|
default:
|
|
return $r('app.string.market_ranking_15min_fall_speed');
|
|
}
|
|
}
|
|
return TAB_METRICS[this.primaryIdx].label;
|
|
}
|
|
|
|
// 成交额/日增仓列用中性色,其余按涨跌上色
|
|
private isNeutralColumn(): boolean {
|
|
const metricId = TAB_METRICS[this.primaryIdx].id;
|
|
return metricId === MetricId.TURN_OVER || metricId === MetricId.INCRE_POSIT;
|
|
}
|
|
|
|
// 第三列展示值:按当前一级 Tab 从对应原始字段取值并格式化
|
|
private thirdColumnValue(item: RankItem): string {
|
|
const metricId = TAB_METRICS[this.primaryIdx].id;
|
|
if (metricId === MetricId.TURN_OVER) {
|
|
return formatGreatNumber(item.turnover);
|
|
}
|
|
if (metricId === MetricId.INCRE_POSIT) {
|
|
return formatGreatNumber(item.incre_posit);
|
|
}
|
|
if (metricId === MetricId.RISE_SPEED || metricId === MetricId.FALL_SPEED) {
|
|
return formatPercent(item.chg_speed);
|
|
}
|
|
return formatPercent(item.price_chg);
|
|
}
|
|
|
|
// 第三列取色:成交额/日增仓走中性色,其余按对应原始字段的正负取色
|
|
private thirdColumnColor(item: RankItem): Resource {
|
|
if (this.isNeutralColumn()) {
|
|
return $r('app.color.elements_text_primary_02');
|
|
}
|
|
const metricId = TAB_METRICS[this.primaryIdx].id;
|
|
const raw = (metricId === MetricId.RISE_SPEED || metricId === MetricId.FALL_SPEED) ? item.chg_speed : item.price_chg;
|
|
return riseFallColor(raw);
|
|
}
|
|
}
|