diff --git a/entry/src/main/ets/ai-pick/AiPickNodeComponent.ets b/entry/src/main/ets/ai-pick/AiPickNodeComponent.ets index 16bcee7..99e5260 100644 --- a/entry/src/main/ets/ai-pick/AiPickNodeComponent.ets +++ b/entry/src/main/ets/ai-pick/AiPickNodeComponent.ets @@ -19,7 +19,6 @@ import { EMPTY_IMAGE_LIGHT, INFO_IMAGE_DARK, INFO_IMAGE_LIGHT, - MAX_ITEMS, RIGHT_ARROW_DARK, RIGHT_ARROW_LIGHT, TITLE_ARROW_DARK, @@ -30,41 +29,34 @@ interface StrategyDescParams { tabIdx: number; } -const CARD_TITLE_HEIGHT = 48; -const TAB_BAR_HEIGHT = 30; -const STRATEGY_DESC_HEIGHT = 86; -const TABLE_HEADER_HEIGHT = 40; -const TABLE_ROW_HEIGHT = 45; -const TABLE_HEIGHT = TABLE_HEADER_HEIGHT + TABLE_ROW_HEIGHT * MAX_ITEMS; -const CUSTOM_STRATEGY_HEIGHT = 30; -const MAIN_CONTENT_HEIGHT = - TAB_BAR_HEIGHT + STRATEGY_DESC_HEIGHT + TABLE_HEIGHT + CUSTOM_STRATEGY_HEIGHT; -const CARD_CONTENT_HEIGHT = CARD_TITLE_HEIGHT + MAIN_CONTENT_HEIGHT; -const NO_CONTRACT_HEIGHT = STRATEGY_DESC_HEIGHT + TABLE_HEIGHT; - @Component export struct AiPickNodeComponent { - // 当前卡片配置。 - @State cardConfig: AllCardsCard | undefined = undefined; - @State cardData: CardData = { tabs: [] }; - @State tabIdx: number = 0; - @StorageLink('enableDarkMode') isDarkMode: boolean = false; - @State tabViewportWidth: number = 0; - @State tabContentWidth: number = 0; - @State tabScrollOffset: number = 0; - @State showExplainSheet: boolean = false; + // 外部状态 @Link @Watch('onRefreshTriggerChange') refreshTrigger: number; @Consume('firstPageVisible') @Watch('onFirstPageVisibleChange') firstPageVisible: boolean = true; + @StorageLink('enableDarkMode') isDarkMode: boolean = false; + + // 卡片业务数据 + @State cardConfig: AllCardsCard | undefined = undefined; + @State cardData: CardData = { tabs: [] }; + + // Tab 状态及滚动区域 + @State tabIdx: number = 0; + @State tabViewportWidth: number = 0; + @State tabContentWidth: number = 0; + @State tabScrollOffset: number = 0; + + // 弹层状态 + @State showExplainSheet: boolean = false; + + // 行情订阅与事件状态 private hqUnsubscribe: (() => void) | undefined; private tcpReconnectListener: EventListener = (): void => { this.onTcpReconnect(); }; - private showTabGradient(): boolean { - const maxOffset = this.tabContentWidth - this.tabViewportWidth; - return maxOffset > 0 && this.tabScrollOffset < maxOffset; - } + // ==================== 组件生命周期 ==================== aboutToAppear(): void { this.updateCardConfig(); @@ -82,7 +74,9 @@ export struct AiPickNodeComponent { this.stopSubscribeHq(); } - // 只负责从公共 Mock 获取并更新卡片配置。 + // ==================== 配置与刷新 ==================== + + // 从首页统一 Mock 配置中读取 AI Pick 配置。 private updateCardConfig(): void { this.cardConfig = AiPickDataFetcher.getInstance().fetchCardConfig(); } @@ -92,6 +86,8 @@ export struct AiPickNodeComponent { this.updateCardData(); } + // ==================== 可见性与 TCP 重连 ==================== + // 页面不可见时停止行情;恢复可见后使用已有策略数据重建订阅。 onFirstPageVisibleChange(): void { if (!this.firstPageVisible) { @@ -118,7 +114,9 @@ export struct AiPickNodeComponent { this.updateCardData(); } - // 只负责请求并更新卡片展示数据。 + // ==================== 数据请求与行情订阅 ==================== + + // 请求策略和合约数据,成功后重建行情订阅。 private async updateCardData(): Promise { this.stopSubscribeHq(); this.cardData = { tabs: [] }; @@ -153,6 +151,110 @@ export struct AiPickNodeComponent { } } + // ==================== 页面跳转 ==================== + + // 跳转到分时详情页。 + // todo: 后续跳转为真实的客户端页面。 + private jumpToDetail(item: PickContractItem): void { + const clientUrl = + `client://client.html?action=ymtz^webid=2205^stockcode=${item.contract}^marketid=${item.market}`; + console.info(`[AiPick] detail url: ${clientUrl}`); + // 当前仍跳转本地 Mock 页面;接入客户端能力后改为使用 clientUrl。 + router.pushUrl({ + url: 'pages/Detail', + params: { + code: item.contract, + market: item.market, + clientUrl: clientUrl, + }, + }); + } + + // 跳转到卡片配置中的标题地址。 + // todo: 后续跳转为真实的客户端页面。 + private jumpToCardTitle(): void { + const androidUrl = this.cardConfig?.card_url.android ?? ''; + const clientUrl = androidUrl !== '' ? androidUrl : this.cardConfig?.card_url.ios ?? ''; + if (clientUrl === '') { + return; + } + console.info(`[AiPick] card title url: ${clientUrl}`); + // 当前仍跳转本地 Mock 页面;接入客户端能力后改为使用 clientUrl。 + router.pushUrl({ + url: 'pages/StrategyDetail', + params: { + type: 'home', + clientUrl: clientUrl, + }, + }); + } + + // 跳转到一句话定制策略主页。 + // todo: 后续跳转为真实的客户端页面。 + private jumpToNewHome(): void { + const pageUrl = 'https://fupage.10jqka.com.cn/ai-web/ai-diagnosis-home.html?sync=1'; + const clientUrl = + `client://client.html?action=ymtz^ishiddenbar=1^mode=new^webid=2804^url=${pageUrl}^title=AI选期`; + console.info(`[AiPick] home url: ${clientUrl}`); + // 当前仍跳转本地 Mock 页面;接入客户端能力后改为使用 clientUrl。 + router.pushUrl({ + url: 'pages/StrategyDetail', + params: { + type: 'home', + clientUrl: clientUrl, + }, + }); + } + + // 跳转到策略详情页。 + // todo: 后续跳转为真实的客户端页面。 + private jumpToStrategyDetail(strategy: PickStrategy): void { + const pageUrl = 'https://fupage.10jqka.com.cn/ai-web/ai-diagnosis-detail.html' + + `?id=${strategy.id}&type=${strategy.periodType}&name=${encodeURIComponent(strategy.name)}`; + const clientUrl = + `client://client.html?action=ymtz^ishiddenbar=1^mode=new^webid=2804^url=${pageUrl}^title=AI选期`; + console.info(`[AiPick] strategy detail url: ${clientUrl}`); + // 当前仍跳转本地 Mock 页面;接入客户端能力后改为使用 clientUrl。 + router.pushUrl({ + url: 'pages/StrategyDetail', + params: { + type: 'detail', + id: strategy.id, + periodType: strategy.periodType, + name: strategy.name, + clientUrl: clientUrl, + }, + }); + } + + // 跳转到策略标签页。 + // todo: 后续跳转为真实的客户端页面。 + private jumpToLabel(strategy: PickStrategy, label: string): void { + const pageUrl = 'https://fupage.10jqka.com.cn/ai-web/ai-diagnosis-label.html' + + `?id=${strategy.id}&type=${strategy.periodType}&label=${encodeURIComponent(label)}`; + const clientUrl = + `client://client.html?action=ymtz^ishiddenbar=1^mode=new^webid=2804^url=${pageUrl}^title=AI选期`; + console.info(`[AiPick] label url: ${clientUrl}`); + // 当前仍跳转本地 Mock 页面;接入客户端能力后改为使用 clientUrl。 + router.pushUrl({ + url: 'pages/StrategyDetail', + params: { + type: 'label', + id: strategy.id, + periodType: strategy.periodType, + label: label, + clientUrl: clientUrl, + }, + }); + } + + // ==================== UI 构建 ==================== + + private showTabGradient(): boolean { + const maxOffset = this.tabContentWidth - this.tabViewportWidth; + return maxOffset > 0 && this.tabScrollOffset < maxOffset; + } + @Builder CardTitle() { Row() { @@ -180,7 +282,7 @@ export struct AiPickNodeComponent { } } .width('100%') - .height(CARD_TITLE_HEIGHT) + .height(48) .alignItems(VerticalAlign.Center) .onClick(() => { this.jumpToCardTitle(); @@ -283,7 +385,7 @@ export struct AiPickNodeComponent { } } .width('100%') - .height(STRATEGY_DESC_HEIGHT) + .height(86) .padding({ top: 10, bottom: 10 }) .alignItems(VerticalAlign.Top) .onClick(() => { @@ -305,7 +407,7 @@ export struct AiPickNodeComponent { .fontColor($r('app.color.text_grey')) } .width('100%') - .height(MAIN_CONTENT_HEIGHT) + .height(321) .alignItems(HorizontalAlign.Center) .justifyContent(FlexAlign.Center) } else { @@ -351,7 +453,7 @@ export struct AiPickNodeComponent { .constraintSize({ minWidth: 66 }) .maxLines(1) .textAlign(TextAlign.Center) - .height(TAB_BAR_HEIGHT) + .height(30) .padding({ left: 8, right: 8 }) .borderRadius(4) .backgroundColor(this.tabIdx === index @@ -367,7 +469,7 @@ export struct AiPickNodeComponent { }) } .width('100%') - .height(TAB_BAR_HEIGHT) + .height(30) .padding({ right: 10 }) .scrollable(ScrollDirection.Horizontal) .scrollBar(BarState.Off) @@ -379,7 +481,7 @@ export struct AiPickNodeComponent { if (this.showTabGradient()) { Row() .width(24) - .height(TAB_BAR_HEIGHT) + .height(30) .linearGradient({ angle: 90, colors: [[$r('app.color.tab_gradient_start'), 0], @@ -389,7 +491,7 @@ export struct AiPickNodeComponent { } } .width('100%') - .height(TAB_BAR_HEIGHT) + .height(30) .onAreaChange((oldValue: Area, newValue: Area) => { this.tabViewportWidth = Number(newValue.width); }) @@ -421,7 +523,7 @@ export struct AiPickNodeComponent { .textAlign(TextAlign.End) } .width('100%') - .height(TABLE_HEADER_HEIGHT) + .height(40) // ── 合约列表 ──────────────────────────────────────── ForEach( @@ -484,7 +586,7 @@ export struct AiPickNodeComponent { .textOverflow({ overflow: TextOverflow.Ellipsis }) } .width('100%') - .height(TABLE_ROW_HEIGHT) + .height(45) .alignItems(VerticalAlign.Center) .border({ width: { top: 1 }, color: $r('app.color.divider_color') }) .onClick(() => { @@ -497,7 +599,7 @@ export struct AiPickNodeComponent { ) } .width('100%') - .height(TABLE_HEIGHT) + .height(175) } // ── 一句话定制策略 ────────────────────────────────────── @@ -512,7 +614,7 @@ export struct AiPickNodeComponent { .margin({ top: 2 }) } .width('100%') - .height(CUSTOM_STRATEGY_HEIGHT) + .height(30) .justifyContent(FlexAlign.Center) .alignItems(VerticalAlign.Center) .onClick(() => { @@ -520,7 +622,7 @@ export struct AiPickNodeComponent { }) } .width('100%') - .height(CARD_CONTENT_HEIGHT) + .height(369) } @Builder @@ -534,103 +636,9 @@ export struct AiPickNodeComponent { .fontColor($r('app.color.text_grey')) } .width('100%') - .height(NO_CONTRACT_HEIGHT) + .height(261) .alignItems(HorizontalAlign.Center) .justifyContent(FlexAlign.Center) } - // 跳转到分时详情页。 - // todo: 后续跳转为真实的客户端页面。 - private jumpToDetail(item: PickContractItem): void { - const clientUrl = - `client://client.html?action=ymtz^webid=2205^stockcode=${item.contract}^marketid=${item.market}`; - console.info(`[AiPick] detail url: ${clientUrl}`); - // 当前仍跳转本地 Mock 页面;接入客户端能力后改为使用 clientUrl。 - router.pushUrl({ - url: 'pages/Detail', - params: { - code: item.contract, - market: item.market, - clientUrl: clientUrl, - }, - }); - } - - // 跳转到卡片配置中的标题地址。 - // todo: 后续跳转为真实的客户端页面。 - private jumpToCardTitle(): void { - const androidUrl = this.cardConfig?.card_url.android ?? ''; - const clientUrl = androidUrl !== '' ? androidUrl : this.cardConfig?.card_url.ios ?? ''; - if (clientUrl === '') { - return; - } - console.info(`[AiPick] card title url: ${clientUrl}`); - // 当前仍跳转本地 Mock 页面;接入客户端能力后改为使用 clientUrl。 - router.pushUrl({ - url: 'pages/StrategyDetail', - params: { - type: 'home', - clientUrl: clientUrl, - }, - }); - } - - // 跳转到一句话定制策略主页。 - // todo: 后续跳转为真实的客户端页面。 - private jumpToNewHome(): void { - const pageUrl = 'https://fupage.10jqka.com.cn/ai-web/ai-diagnosis-home.html?sync=1'; - const clientUrl = - `client://client.html?action=ymtz^ishiddenbar=1^mode=new^webid=2804^url=${pageUrl}^title=AI选期`; - console.info(`[AiPick] home url: ${clientUrl}`); - // 当前仍跳转本地 Mock 页面;接入客户端能力后改为使用 clientUrl。 - router.pushUrl({ - url: 'pages/StrategyDetail', - params: { - type: 'home', - clientUrl: clientUrl, - }, - }); - } - - // 跳转到策略详情页。 - // todo: 后续跳转为真实的客户端页面。 - private jumpToStrategyDetail(strategy: PickStrategy): void { - const pageUrl = 'https://fupage.10jqka.com.cn/ai-web/ai-diagnosis-detail.html' + - `?id=${strategy.id}&type=${strategy.periodType}&name=${encodeURIComponent(strategy.name)}`; - const clientUrl = - `client://client.html?action=ymtz^ishiddenbar=1^mode=new^webid=2804^url=${pageUrl}^title=AI选期`; - console.info(`[AiPick] strategy detail url: ${clientUrl}`); - // 当前仍跳转本地 Mock 页面;接入客户端能力后改为使用 clientUrl。 - router.pushUrl({ - url: 'pages/StrategyDetail', - params: { - type: 'detail', - id: strategy.id, - periodType: strategy.periodType, - name: strategy.name, - clientUrl: clientUrl, - }, - }); - } - - // 跳转到策略标签页。 - // todo: 后续跳转为真实的客户端页面。 - private jumpToLabel(strategy: PickStrategy, label: string): void { - const pageUrl = 'https://fupage.10jqka.com.cn/ai-web/ai-diagnosis-label.html' + - `?id=${strategy.id}&type=${strategy.periodType}&label=${encodeURIComponent(label)}`; - const clientUrl = - `client://client.html?action=ymtz^ishiddenbar=1^mode=new^webid=2804^url=${pageUrl}^title=AI选期`; - console.info(`[AiPick] label url: ${clientUrl}`); - // 当前仍跳转本地 Mock 页面;接入客户端能力后改为使用 clientUrl。 - router.pushUrl({ - url: 'pages/StrategyDetail', - params: { - type: 'label', - id: strategy.id, - periodType: strategy.periodType, - label: label, - clientUrl: clientUrl, - }, - }); - } }