feat(market-ranking): 完善行情订阅生命周期
This commit is contained in:
@@ -42,6 +42,34 @@ doc/ HTTP 与 4106 行情接口文档
|
|||||||
市场排名与 AI 选期分别通过独立的 `HqRequestClient` 构造行情订阅参数,共享
|
市场排名与 AI 选期分别通过独立的 `HqRequestClient` 构造行情订阅参数,共享
|
||||||
`common/HqFieldId.ets` 中的 4106 字段 ID。
|
`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
|
当前 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 { MarketRankingHqRequestClient } from './MarketRankingHqRequestClient';
|
||||||
import { formatGreatNumber, formatPercent, riseFallColor } from '../common/NumberFormat';
|
import { formatGreatNumber, formatPercent, riseFallColor } from '../common/NumberFormat';
|
||||||
import { AllCardsCard } from '../common/AllCardsModels';
|
import { AllCardsCard } from '../common/AllCardsModels';
|
||||||
|
import {
|
||||||
|
emitter,
|
||||||
|
EVENT_TCP_RECONNECT,
|
||||||
|
EventListener,
|
||||||
|
} from '../common/EventEmitter';
|
||||||
import {
|
import {
|
||||||
INFO_IMAGE_DARK,
|
INFO_IMAGE_DARK,
|
||||||
INFO_IMAGE_LIGHT,
|
INFO_IMAGE_LIGHT,
|
||||||
@@ -32,11 +37,20 @@ export struct MarketRankingNodeComponent {
|
|||||||
@State tabContentWidth: number = 0;
|
@State tabContentWidth: number = 0;
|
||||||
@State tabScrollOffset: number = 0;
|
@State tabScrollOffset: number = 0;
|
||||||
@State showExplainSheet: boolean = false;
|
@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 hqUnsubscribe: (() => void) | undefined;
|
||||||
|
private tcpReconnectListener: EventListener = (): void => {
|
||||||
|
this.onTcpReconnect();
|
||||||
|
};
|
||||||
|
|
||||||
aboutToAppear(): void {
|
aboutToAppear(): void {
|
||||||
this.updateCardConfig();
|
this.updateCardConfig();
|
||||||
|
emitter.on(EVENT_TCP_RECONNECT, this.tcpReconnectListener);
|
||||||
|
if (!this.firstPageVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Mock 网络延迟:模拟真实接口返回前的等待过程,接入真实请求后移除该 setTimeout。
|
// Mock 网络延迟:模拟真实接口返回前的等待过程,接入真实请求后移除该 setTimeout。
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.updateCardData(this.primaryIdx, this.secondaryIdx);
|
this.updateCardData(this.primaryIdx, this.secondaryIdx);
|
||||||
@@ -44,6 +58,7 @@ export struct MarketRankingNodeComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
aboutToDisappear(): void {
|
aboutToDisappear(): void {
|
||||||
|
emitter.off(EVENT_TCP_RECONNECT, this.tcpReconnectListener);
|
||||||
this.stopSubscribeHq();
|
this.stopSubscribeHq();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +67,37 @@ export struct MarketRankingNodeComponent {
|
|||||||
this.cardConfig = MarketRankingDataFetcher.getInstance().fetchCardConfig();
|
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
|
@Builder
|
||||||
CardTitle() {
|
CardTitle() {
|
||||||
Row({ space: 4 }) {
|
Row({ space: 4 }) {
|
||||||
@@ -361,7 +407,9 @@ export struct MarketRankingNodeComponent {
|
|||||||
name: contract.contract_name,
|
name: contract.contract_name,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
this.subscribeHq(primaryIdx, secondaryIdx);
|
if (this.firstPageVisible) {
|
||||||
|
this.subscribeHq(primaryIdx, secondaryIdx);
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
this.cardData = { tableList: [] };
|
this.cardData = { tableList: [] };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import { AiPickNodeComponent } from '../ai-pick/AiPickNodeComponent';
|
|||||||
@Entry
|
@Entry
|
||||||
@Component
|
@Component
|
||||||
struct Index {
|
struct Index {
|
||||||
|
@State refreshTrigger: number = 0;
|
||||||
|
@Provide('firstPageVisible') firstPageVisible: boolean = true;
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
Scroll() {
|
Scroll() {
|
||||||
Column({ space: 8 }) {
|
Column({ space: 8 }) {
|
||||||
@@ -13,7 +16,9 @@ struct Index {
|
|||||||
AIView()
|
AIView()
|
||||||
}
|
}
|
||||||
Card() {
|
Card() {
|
||||||
MarketRankingNodeComponent()
|
MarketRankingNodeComponent({
|
||||||
|
refreshTrigger: $refreshTrigger,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Card() {
|
Card() {
|
||||||
AiPickNodeComponent()
|
AiPickNodeComponent()
|
||||||
|
|||||||
Reference in New Issue
Block a user