feat(market-ranking): 完善行情订阅生命周期
This commit is contained in:
@@ -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,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()
|
||||
|
||||
Reference in New Issue
Block a user