feat(market-ranking): 完善行情订阅生命周期

This commit is contained in:
clz
2026-07-24 14:51:34 +08:00
parent 6c338fc497
commit 8e1a1f1eee
4 changed files with 137 additions and 3 deletions
+28
View File
@@ -42,6 +42,34 @@ doc/ HTTP 与 4106 行情接口文档
市场排名与 AI 选期分别通过独立的 `HqRequestClient` 构造行情订阅参数,共享
`common/HqFieldId.ets` 中的 4106 字段 ID。
## MarketRanking 生命周期
`MarketRankingNodeComponent` 出现时读取卡片配置、注册 `tcpReconnect` 事件监听,并在
`firstPageVisible``true` 时获取当前 Tab 的 contracts。contracts 返回后先生成占位列表,
再由 `MarketRankingHqRequestClient` 发起行情订阅,行情回调会整体替换 `CardData`
```text
aboutToAppear
→ 注册 TCP 重连事件
→ 获取当前 Tab 的 contracts
→ 填充合约占位列表
→ 页面可见时订阅行情
```
运行期间按以下规则更新:
- `refreshTrigger`:由父组件通过 `@Link` 传入;变化后停止旧行情、重新获取 contracts,
并在可见时重新订阅。
- `firstPageVisible`:由父组件通过 `@Provide` 提供,组件通过 `@Consume` 监听,默认
`true`。变为不可见时立即停止行情;恢复可见时优先使用已有 contracts 重新订阅,
没有 contracts 才重新请求。
- `tcpReconnect`:组件通过公共 `emitter` 监听。重连且页面可见时停止旧行情并重新订阅;
页面不可见时忽略,恢复可见后再订阅。
- Tab 切换:停止旧行情、清空列表、获取新 Tab 的 contracts,再订阅对应行情。
`aboutToDisappear` 会解绑 TCP 重连监听并停止行情订阅。HTTP 请求等待期间如果页面变为
不可见,contracts 仍可更新,但不会创建行情订阅。
## 数据与接口
当前 HTTP Fetcher 仅构造请求参数,行情客户端也尚未连接真实客户端桥接层;页面使用 Mock
@@ -0,0 +1,53 @@
export const EVENT_TCP_RECONNECT = 'tcpReconnect';
export type EventListener = () => void;
// 模拟客户端事件总线,后续可在真实 TCP 重连回调中调用 emit。
export class EventEmitter {
private static readonly instance: EventEmitter = new EventEmitter();
private listeners: Map<string, EventListener[]> = new Map<string, EventListener[]>();
private constructor() {
}
static getInstance(): EventEmitter {
return EventEmitter.instance;
}
on(eventName: string, listener: EventListener): void {
const eventListeners = this.listeners.get(eventName);
if (eventListeners === undefined) {
this.listeners.set(eventName, [listener]);
return;
}
if (!eventListeners.includes(listener)) {
eventListeners.push(listener);
}
}
off(eventName: string, listener: EventListener): void {
const eventListeners = this.listeners.get(eventName);
if (eventListeners === undefined) {
return;
}
const listenerIndex = eventListeners.indexOf(listener);
if (listenerIndex >= 0) {
eventListeners.splice(listenerIndex, 1);
}
if (eventListeners.length === 0) {
this.listeners.delete(eventName);
}
}
emit(eventName: string): void {
const eventListeners = this.listeners.get(eventName);
if (eventListeners === undefined) {
return;
}
eventListeners.slice().forEach((listener: EventListener): void => {
listener();
});
}
}
export const emitter: EventEmitter = EventEmitter.getInstance();
@@ -8,6 +8,11 @@ import { MarketRankingDataFetcher } from './MarketRankingDataFetcher';
import { MarketRankingHqRequestClient } from './MarketRankingHqRequestClient';
import { formatGreatNumber, formatPercent, riseFallColor } from '../common/NumberFormat';
import { AllCardsCard } from '../common/AllCardsModels';
import {
emitter,
EVENT_TCP_RECONNECT,
EventListener,
} from '../common/EventEmitter';
import {
INFO_IMAGE_DARK,
INFO_IMAGE_LIGHT,
@@ -32,11 +37,20 @@ export struct MarketRankingNodeComponent {
@State tabContentWidth: number = 0;
@State tabScrollOffset: number = 0;
@State showExplainSheet: boolean = false;
@StorageLink('enableDarkMode') isDarkMode: boolean = false;
@Link @Watch('onRefreshTriggerChange') refreshTrigger: number; //todo
@Consume('firstPageVisible') @Watch('onFirstPageVisibleChange') firstPageVisible: boolean = true;
@StorageLink('enableDarkMode') isDarkMode: boolean = false; //todo
private hqUnsubscribe: (() => void) | undefined;
private tcpReconnectListener: EventListener = (): void => {
this.onTcpReconnect();
};
aboutToAppear(): void {
this.updateCardConfig();
emitter.on(EVENT_TCP_RECONNECT, this.tcpReconnectListener);
if (!this.firstPageVisible) {
return;
}
// Mock 网络延迟:模拟真实接口返回前的等待过程,接入真实请求后移除该 setTimeout。
setTimeout(() => {
this.updateCardData(this.primaryIdx, this.secondaryIdx);
@@ -44,6 +58,7 @@ export struct MarketRankingNodeComponent {
}
aboutToDisappear(): void {
emitter.off(EVENT_TCP_RECONNECT, this.tcpReconnectListener);
this.stopSubscribeHq();
}
@@ -52,6 +67,37 @@ export struct MarketRankingNodeComponent {
this.cardConfig = MarketRankingDataFetcher.getInstance().fetchCardConfig();
}
// refreshTrigger 变化时重新获取当前 Tab 的合约,并在请求完成后重建行情订阅。
onRefreshTriggerChange(): void {
this.updateCardData(this.primaryIdx, this.secondaryIdx);
}
// 页面不可见时停止行情;恢复可见后使用已有合约重建订阅。
onFirstPageVisibleChange(): void {
if (!this.firstPageVisible) {
this.stopSubscribeHq();
return;
}
if (this.cardData.tableList.length > 0) {
this.subscribeHq(this.primaryIdx, this.secondaryIdx);
return;
}
this.updateCardData(this.primaryIdx, this.secondaryIdx);
}
// 收到 TCP 重连事件后,仅在页面可见时重新订阅行情。
private onTcpReconnect(): void {
if (!this.firstPageVisible) {
return;
}
this.stopSubscribeHq();
if (this.cardData.tableList.length > 0) {
this.subscribeHq(this.primaryIdx, this.secondaryIdx);
return;
}
this.updateCardData(this.primaryIdx, this.secondaryIdx);
}
@Builder
CardTitle() {
Row({ space: 4 }) {
@@ -361,7 +407,9 @@ export struct MarketRankingNodeComponent {
name: contract.contract_name,
})),
};
this.subscribeHq(primaryIdx, secondaryIdx);
if (this.firstPageVisible) {
this.subscribeHq(primaryIdx, secondaryIdx);
}
} catch {
this.cardData = { tableList: [] };
}
+6 -1
View File
@@ -6,6 +6,9 @@ import { AiPickNodeComponent } from '../ai-pick/AiPickNodeComponent';
@Entry
@Component
struct Index {
@State refreshTrigger: number = 0;
@Provide('firstPageVisible') firstPageVisible: boolean = true;
build() {
Scroll() {
Column({ space: 8 }) {
@@ -13,7 +16,9 @@ struct Index {
AIView()
}
Card() {
MarketRankingNodeComponent()
MarketRankingNodeComponent({
refreshTrigger: $refreshTrigger,
})
}
Card() {
AiPickNodeComponent()