Compare commits

...
11 Commits
6 changed files with 254 additions and 169 deletions
@@ -27,11 +27,6 @@ export const INFO_IMAGE_LIGHT =
'https://u.thsi.cn/imgsrc/bbs/e1e9b6d81b9f4f6fa817cbeb71e98ff1.png'; 'https://u.thsi.cn/imgsrc/bbs/e1e9b6d81b9f4f6fa817cbeb71e98ff1.png';
export const INFO_IMAGE_DARK = export const INFO_IMAGE_DARK =
'https://u.thsi.cn/imgsrc/bbs/19f7f20bcf800ec2b8e9fc0fa0d73369.png'; 'https://u.thsi.cn/imgsrc/bbs/19f7f20bcf800ec2b8e9fc0fa0d73369.png';
export const EMPTY_IMAGE_LIGHT =
'https://u.thsi.cn/imgsrc/bbs/8eee9cd4fa7e359032de11a3f7c2a70b_300_230.png';
export const EMPTY_IMAGE_DARK =
'https://u.thsi.cn/imgsrc/bbs/1c4e679244ed87231befad2b04cddfe2_300_230.png';
// 字段黑名单:过滤 field_list 中不应展示的字段 // 字段黑名单:过滤 field_list 中不应展示的字段
export const BLACK_LIST: string[] = [ export const BLACK_LIST: string[] = [
'合约代码', '合约代码',
+12 -18
View File
@@ -25,47 +25,45 @@ export const MOCK_RAW_TABS: RawPickTabItem[] = [
{ contract: 'ZN2506', market: '70', contract_name: '沪锌2506', main_contract_type: '1' }, { contract: 'ZN2506', market: '70', contract_name: '沪锌2506', main_contract_type: '1' },
], ],
field_list: [ field_list: [
{ key: '仓单单日增减量', type: 'DOUBLE', unit: '张' },
{ key: '成交量', type: 'DOUBLE', unit: '手' }, { key: '成交量', type: 'DOUBLE', unit: '手' },
{ key: '持仓量', type: 'DOUBLE', unit: '手' }, { key: '持仓量', type: 'DOUBLE', unit: '手' },
{ key: '合约代码', type: 'STRING', unit: '' }, // 黑名单,不展示 { key: '合约代码', type: 'STRING', unit: '' }, // 黑名单,不展示
{ key: '最新涨跌幅', type: 'DOUBLE', unit: '%' }, // 黑名单,不展示 { key: '最新涨跌幅', type: 'DOUBLE', unit: '%' }, // 黑名单,不展示
], ],
origin_result_list: [ origin_result_list: [
{ '成交量': 123000, '持仓量': 456000, '合约代码': 'CU2506', '最新涨跌幅': 1.85 }, { '仓单单日增减量': 1250, '成交量': 123000, '持仓量': 456000, '合约代码': 'CU2506', '最新涨跌幅': 1.85 },
{ '成交量': 31000, '持仓量': 182000, '合约代码': 'CU2507', '最新涨跌幅': 1.62 }, { '仓单单日增减量': -860, '成交量': 31000, '持仓量': 182000, '合约代码': 'CU2507', '最新涨跌幅': 1.62 },
{ '成交量': 57000, '持仓量': 224000, '合约代码': 'NI2506', '最新涨跌幅': 0.94 }, { '仓单单日增减量': 420, '成交量': 57000, '持仓量': 224000, '合约代码': 'NI2506', '最新涨跌幅': 0.94 },
{ '成交量': 44000, '持仓量': 198000, '合约代码': 'AL2506', '最新涨跌幅': 0.76 }, { '仓单单日增减量': -315, '成交量': 44000, '持仓量': 198000, '合约代码': 'AL2506', '最新涨跌幅': 0.76 },
{ '成交量': 28000, '持仓量': 143000, '合约代码': 'ZN2506', '最新涨跌幅': 0.61 }, { '仓单单日增减量': 980, '成交量': 28000, '持仓量': 143000, '合约代码': 'ZN2506', '最新涨跌幅': 0.61 },
], ],
}, },
}, },
// ── Tab 2:系统周期策略(type = 'system_period')────────────────── // ── Tab 2:只有1条数据的系统策略 ──────────────────
{ {
type: 'system_period', type: 'system_period',
system_strategy: { system_strategy: {
system_strategy: { system_strategy: {
id: 'strat_002', id: 'strat_002',
strategy_name: '黑色系超跌反弹', strategy_name: '黑色系超跌反弹',
content: '铁矿石、螺纹钢跌幅已达技术支撑位,RSI 超卖信号显现', content: '铁矿石跌幅已达技术支撑位,RSI 超卖信号显现',
strategy_type: 'system_period', strategy_type: 'system_period',
tag_list: ['反弹', '黑色系', '超卖'], tag_list: ['反弹', '黑色系', '超卖'],
white_img: 'https://s.thsi.cn/js/m/upload/yyzt/1721828556_3492.png', white_img: 'https://s.thsi.cn/js/m/upload/yyzt/1721828556_3492.png',
black_img: 'https://s.thsi.cn/js/m/upload/yyzt/1721828547_6904.png', black_img: 'https://s.thsi.cn/js/m/upload/yyzt/1721828547_6904.png',
}, },
detail_count: 3, detail_count: 1,
detail_list: [ detail_list: [
{ contract: 'RB2510', market: '70', contract_name: '螺纹钢2510', main_contract_type: '1' }, { contract: 'RB2510', market: '70', contract_name: '螺纹钢2510', main_contract_type: '1' },
{ contract: 'I2509', market: '70', contract_name: '铁矿石2509', main_contract_type: '1' },
{ contract: 'HC2510', market: '70', contract_name: '热卷2510', main_contract_type: '0' },
], ],
// 系统策略无自定义 DOUBLE 字段
field_list: [], field_list: [],
origin_result_list: [], origin_result_list: [],
}, },
}, },
// ── Tab 3:自定义策略(type = 'custom_strategy')────────────────── // ── Tab 3:只有2条数据的自定义策略 ──────────────────
{ {
type: 'custom_strategy', type: 'custom_strategy',
custom_strategy: { custom_strategy: {
@@ -76,12 +74,10 @@ export const MOCK_RAW_TABS: RawPickTabItem[] = [
strategy_type: 'custom_strategy', strategy_type: 'custom_strategy',
tag_list: ['季节性', '农产品'], tag_list: ['季节性', '农产品'],
}, },
detail_count: 4, detail_count: 2,
detail_list: [ detail_list: [
{ contract: 'M2509', market: '70', contract_name: '豆粕2509', main_contract_type: '1' }, { contract: 'M2509', market: '70', contract_name: '豆粕2509', main_contract_type: '1' },
{ contract: 'RM2509', market: '70', contract_name: '菜粕2509', main_contract_type: '1' }, { contract: 'RM2509', market: '70', contract_name: '菜粕2509', main_contract_type: '0' },
{ contract: 'Y2509', market: '70', contract_name: '豆油2509', main_contract_type: '1' },
{ contract: 'OI2509', market: '70', contract_name: '菜油2509', main_contract_type: '1' },
], ],
field_list: [ field_list: [
{ key: '成交量', type: 'DOUBLE', unit: '手' }, { key: '成交量', type: 'DOUBLE', unit: '手' },
@@ -90,8 +86,6 @@ export const MOCK_RAW_TABS: RawPickTabItem[] = [
origin_result_list: [ origin_result_list: [
{ '成交量': 986000, '持仓量': 3102000 }, { '成交量': 986000, '持仓量': 3102000 },
{ '成交量': 324000, '持仓量': 1258000 }, { '成交量': 324000, '持仓量': 1258000 },
{ '成交量': 189000, '持仓量': 683000 },
{ '成交量': 142000, '持仓量': 521000 },
], ],
}, },
}, },
+165 -139
View File
@@ -15,11 +15,8 @@ import {
EventListener, EventListener,
} from '../common/EventEmitter'; } from '../common/EventEmitter';
import { import {
EMPTY_IMAGE_DARK,
EMPTY_IMAGE_LIGHT,
INFO_IMAGE_DARK, INFO_IMAGE_DARK,
INFO_IMAGE_LIGHT, INFO_IMAGE_LIGHT,
MAX_ITEMS,
RIGHT_ARROW_DARK, RIGHT_ARROW_DARK,
RIGHT_ARROW_LIGHT, RIGHT_ARROW_LIGHT,
TITLE_ARROW_DARK, TITLE_ARROW_DARK,
@@ -30,41 +27,38 @@ interface StrategyDescParams {
tabIdx: number; 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 @Component
export struct AiPickNodeComponent { 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; @Link @Watch('onRefreshTriggerChange') refreshTrigger: number;
@Consume('firstPageVisible') @Watch('onFirstPageVisibleChange') @Consume('firstPageVisible') @Watch('onFirstPageVisibleChange')
firstPageVisible: boolean = true; firstPageVisible: boolean = true;
@StorageLink('enableDarkMode') isDarkMode: boolean = false;
// 卡片业务数据
@State cardConfig: AllCardsCard | undefined = undefined;
@State cardData: CardData = { tabs: [] };
// Tab 状态及滚动区域
@State tabIdx: number = 0;
private getUrl(resource: Resource): string {
return getContext(this).resourceManager.getStringSync(resource);
}
@State tabViewportWidth: number = 0;
@State tabContentWidth: number = 0;
@State tabScrollOffset: number = 0;
// 弹层状态
@State showExplainSheet: boolean = false;
// 行情订阅与事件状态
private hqUnsubscribe: (() => void) | undefined; private hqUnsubscribe: (() => void) | undefined;
private tcpReconnectListener: EventListener = (): void => { private tcpReconnectListener: EventListener = (): void => {
this.onTcpReconnect(); this.onTcpReconnect();
}; };
private showTabGradient(): boolean { // ==================== 组件生命周期 ====================
const maxOffset = this.tabContentWidth - this.tabViewportWidth;
return maxOffset > 0 && this.tabScrollOffset < maxOffset;
}
aboutToAppear(): void { aboutToAppear(): void {
this.updateCardConfig(); this.updateCardConfig();
@@ -82,7 +76,9 @@ export struct AiPickNodeComponent {
this.stopSubscribeHq(); this.stopSubscribeHq();
} }
// 只负责从公共 Mock 获取并更新卡片配置。 // ==================== 配置与刷新 ====================
// 从首页统一 Mock 配置中读取 AI Pick 配置。
private updateCardConfig(): void { private updateCardConfig(): void {
this.cardConfig = AiPickDataFetcher.getInstance().fetchCardConfig(); this.cardConfig = AiPickDataFetcher.getInstance().fetchCardConfig();
} }
@@ -92,6 +88,8 @@ export struct AiPickNodeComponent {
this.updateCardData(); this.updateCardData();
} }
// ==================== 可见性与 TCP 重连 ====================
// 页面不可见时停止行情;恢复可见后使用已有策略数据重建订阅。 // 页面不可见时停止行情;恢复可见后使用已有策略数据重建订阅。
onFirstPageVisibleChange(): void { onFirstPageVisibleChange(): void {
if (!this.firstPageVisible) { if (!this.firstPageVisible) {
@@ -118,7 +116,9 @@ export struct AiPickNodeComponent {
this.updateCardData(); this.updateCardData();
} }
// 只负责请求并更新卡片展示数据。 // ==================== 数据请求与行情订阅 ====================
// 请求策略和合约数据,成功后重建行情订阅。
private async updateCardData(): Promise<void> { private async updateCardData(): Promise<void> {
this.stopSubscribeHq(); this.stopSubscribeHq();
this.cardData = { tabs: [] }; this.cardData = { tabs: [] };
@@ -153,6 +153,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: string = this.getUrl($r('app.string.ai_pick_home_url'));
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: string = this.getUrl($r('app.string.ai_pick_strategy_detail_url')) +
`?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: string = this.getUrl($r('app.string.ai_pick_strategy_label_url')) +
`?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 @Builder
CardTitle() { CardTitle() {
Row() { Row() {
@@ -180,7 +284,7 @@ export struct AiPickNodeComponent {
} }
} }
.width('100%') .width('100%')
.height(CARD_TITLE_HEIGHT) .height(48)
.alignItems(VerticalAlign.Center) .alignItems(VerticalAlign.Center)
.onClick(() => { .onClick(() => {
this.jumpToCardTitle(); this.jumpToCardTitle();
@@ -283,7 +387,7 @@ export struct AiPickNodeComponent {
} }
} }
.width('100%') .width('100%')
.height(STRATEGY_DESC_HEIGHT) .height(86)
.padding({ top: 10, bottom: 10 }) .padding({ top: 10, bottom: 10 })
.alignItems(VerticalAlign.Top) .alignItems(VerticalAlign.Top)
.onClick(() => { .onClick(() => {
@@ -305,7 +409,7 @@ export struct AiPickNodeComponent {
.fontColor($r('app.color.text_grey')) .fontColor($r('app.color.text_grey'))
} }
.width('100%') .width('100%')
.height(MAIN_CONTENT_HEIGHT) .height(321)
.alignItems(HorizontalAlign.Center) .alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center) .justifyContent(FlexAlign.Center)
} else { } else {
@@ -351,7 +455,7 @@ export struct AiPickNodeComponent {
.constraintSize({ minWidth: 66 }) .constraintSize({ minWidth: 66 })
.maxLines(1) .maxLines(1)
.textAlign(TextAlign.Center) .textAlign(TextAlign.Center)
.height(TAB_BAR_HEIGHT) .height(30)
.padding({ left: 8, right: 8 }) .padding({ left: 8, right: 8 })
.borderRadius(4) .borderRadius(4)
.backgroundColor(this.tabIdx === index .backgroundColor(this.tabIdx === index
@@ -367,7 +471,7 @@ export struct AiPickNodeComponent {
}) })
} }
.width('100%') .width('100%')
.height(TAB_BAR_HEIGHT) .height(30)
.padding({ right: 10 }) .padding({ right: 10 })
.scrollable(ScrollDirection.Horizontal) .scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off) .scrollBar(BarState.Off)
@@ -379,7 +483,7 @@ export struct AiPickNodeComponent {
if (this.showTabGradient()) { if (this.showTabGradient()) {
Row() Row()
.width(24) .width(24)
.height(TAB_BAR_HEIGHT) .height(30)
.linearGradient({ .linearGradient({
angle: 90, angle: 90,
colors: [[$r('app.color.tab_gradient_start'), 0], colors: [[$r('app.color.tab_gradient_start'), 0],
@@ -389,7 +493,7 @@ export struct AiPickNodeComponent {
} }
} }
.width('100%') .width('100%')
.height(TAB_BAR_HEIGHT) .height(30)
.onAreaChange((oldValue: Area, newValue: Area) => { .onAreaChange((oldValue: Area, newValue: Area) => {
this.tabViewportWidth = Number(newValue.width); this.tabViewportWidth = Number(newValue.width);
}) })
@@ -407,21 +511,33 @@ export struct AiPickNodeComponent {
Row({ space: 8 }) { Row({ space: 8 }) {
Text(formatColName(this.cardData.tabs[this.tabIdx].keysList[0])) Text(formatColName(this.cardData.tabs[this.tabIdx].keysList[0]))
.fontSize(14) .fontSize(14)
.maxFontSize(14)
.minFontSize(9)
.maxLines(1)
.heightAdaptivePolicy(TextHeightAdaptivePolicy.MIN_FONT_SIZE_FIRST)
.fontColor($r('app.color.text_tertiary')) .fontColor($r('app.color.text_tertiary'))
.width('35%') .width('35%')
Text(formatColName(this.cardData.tabs[this.tabIdx].keysList[1])) Text(formatColName(this.cardData.tabs[this.tabIdx].keysList[1]))
.fontSize(14) .fontSize(14)
.maxFontSize(14)
.minFontSize(9)
.maxLines(1)
.heightAdaptivePolicy(TextHeightAdaptivePolicy.MIN_FONT_SIZE_FIRST)
.fontColor($r('app.color.text_tertiary')) .fontColor($r('app.color.text_tertiary'))
.layoutWeight(1) .layoutWeight(1)
.textAlign(TextAlign.End) .textAlign(TextAlign.End)
Text(formatColName(this.cardData.tabs[this.tabIdx].keysList[2])) Text(formatColName(this.cardData.tabs[this.tabIdx].keysList[2]))
.fontSize(14) .fontSize(14)
.maxFontSize(14)
.minFontSize(9)
.maxLines(1)
.heightAdaptivePolicy(TextHeightAdaptivePolicy.MIN_FONT_SIZE_FIRST)
.fontColor($r('app.color.text_tertiary')) .fontColor($r('app.color.text_tertiary'))
.layoutWeight(1) .layoutWeight(1)
.textAlign(TextAlign.End) .textAlign(TextAlign.End)
} }
.width('100%') .width('100%')
.height(TABLE_HEADER_HEIGHT) .height(40)
// ── 合约列表 ──────────────────────────────────────── // ── 合约列表 ────────────────────────────────────────
ForEach( ForEach(
@@ -434,7 +550,6 @@ export struct AiPickNodeComponent {
.maxFontSize(16) .maxFontSize(16)
.minFontSize(9) .minFontSize(9)
.fontColor($r('app.color.text_primary')) .fontColor($r('app.color.text_primary'))
.layoutWeight(1)
.maxLines(1) .maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis }) .textOverflow({ overflow: TextOverflow.Ellipsis })
if (item.isMain) { if (item.isMain) {
@@ -484,7 +599,7 @@ export struct AiPickNodeComponent {
.textOverflow({ overflow: TextOverflow.Ellipsis }) .textOverflow({ overflow: TextOverflow.Ellipsis })
} }
.width('100%') .width('100%')
.height(TABLE_ROW_HEIGHT) .height(45)
.alignItems(VerticalAlign.Center) .alignItems(VerticalAlign.Center)
.border({ width: { top: 1 }, color: $r('app.color.divider_color') }) .border({ width: { top: 1 }, color: $r('app.color.divider_color') })
.onClick(() => { .onClick(() => {
@@ -495,9 +610,13 @@ export struct AiPickNodeComponent {
(item: PickContractItem) => (item: PickContractItem) =>
`${item.contract}_${item.market}_${item.price ?? ''}_${item.priceChg ?? ''}`, `${item.contract}_${item.market}_${item.price ?? ''}_${item.priceChg ?? ''}`,
) )
Divider()
.width('100%')
.strokeWidth(1)
.color($r('app.color.divider_color'))
} }
.width('100%') .width('100%')
.height(TABLE_HEIGHT)
} }
// ── 一句话定制策略 ────────────────────────────────────── // ── 一句话定制策略 ──────────────────────────────────────
@@ -512,7 +631,7 @@ export struct AiPickNodeComponent {
.margin({ top: 2 }) .margin({ top: 2 })
} }
.width('100%') .width('100%')
.height(CUSTOM_STRATEGY_HEIGHT) .height(30)
.justifyContent(FlexAlign.Center) .justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center) .alignItems(VerticalAlign.Center)
.onClick(() => { .onClick(() => {
@@ -520,13 +639,14 @@ export struct AiPickNodeComponent {
}) })
} }
.width('100%') .width('100%')
.height(CARD_CONTENT_HEIGHT)
} }
@Builder @Builder
EmptyState(message: ResourceStr) { EmptyState(message: ResourceStr) {
Column() { Column() {
Image(this.isDarkMode ? EMPTY_IMAGE_DARK : EMPTY_IMAGE_LIGHT) Image(this.isDarkMode ?
$r('app.string.ai_pick_empty_image_dark_url') :
$r('app.string.ai_pick_empty_image_light_url'))
.width(100) .width(100)
.height(100) .height(100)
Text(message) Text(message)
@@ -534,103 +654,9 @@ export struct AiPickNodeComponent {
.fontColor($r('app.color.text_grey')) .fontColor($r('app.color.text_grey'))
} }
.width('100%') .width('100%')
.height(NO_CONTRACT_HEIGHT) .height(261)
.alignItems(HorizontalAlign.Center) .alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.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,
},
});
}
} }
@@ -1,4 +1,6 @@
import router from '@ohos.router'; import router from '@ohos.router';
import { common } from '@kit.AbilityKit';
import { preferences } from '@kit.ArkData';
import { import {
CardData, CardData,
PeriodRange, PeriodRange,
@@ -28,9 +30,8 @@ import {
const NORMAL_SIZE = 3; const NORMAL_SIZE = 3;
const MAX_SIZE = 5; const MAX_SIZE = 5;
const UNFOLDED_STORAGE_KEY = 'firstPageMarketRankingUnfolded'; const SP_FILE_NAME_FIRST_PAGE_MARKET_RANKING = 'sp_file_name_first_page_market_ranking';
const SP_KEY_FIRST_PAGE_MARKET_RANKING_EXPAND = 'sp_key_first_page_market_ranking_expand';
PersistentStorage.persistProp<boolean>(UNFOLDED_STORAGE_KEY, false);
@Component @Component
export struct MarketRankingNodeComponent { export struct MarketRankingNodeComponent {
@@ -42,7 +43,7 @@ export struct MarketRankingNodeComponent {
// Tab 选中/展开状态是纯 UI 交互状态,留在 View 里 // Tab 选中/展开状态是纯 UI 交互状态,留在 View 里
@State primaryIdx: number = 0; @State primaryIdx: number = 0;
@State secondaryIdx: number = 0; @State secondaryIdx: number = 0;
@StorageLink(UNFOLDED_STORAGE_KEY) unfolded: boolean = false; @State unfolded: boolean = false;
@State tabViewportWidth: number = 0; @State tabViewportWidth: number = 0;
@State tabContentWidth: number = 0; @State tabContentWidth: number = 0;
@State tabScrollOffset: number = 0; @State tabScrollOffset: number = 0;
@@ -56,6 +57,7 @@ export struct MarketRankingNodeComponent {
}; };
aboutToAppear(): void { aboutToAppear(): void {
this.initUnfoldedState();
this.updateCardConfig(); this.updateCardConfig();
emitter.on(EVENT_TCP_RECONNECT, this.tcpReconnectListener); emitter.on(EVENT_TCP_RECONNECT, this.tcpReconnectListener);
if (!this.firstPageVisible) { if (!this.firstPageVisible) {
@@ -527,6 +529,46 @@ export struct MarketRankingNodeComponent {
// 以下是UI相关 // 以下是UI相关
private toggleUnfolded(): void { private toggleUnfolded(): void {
this.unfolded = !this.unfolded; this.unfolded = !this.unfolded;
try {
const store: preferences.Preferences | undefined = this.getPreferenceStore();
if (store === undefined) {
return;
}
store.putSync(SP_KEY_FIRST_PAGE_MARKET_RANKING_EXPAND, this.unfolded);
store.flush().catch((error: Error) => {
console.error(`[MarketRanking] persist unfolded state failed: ${error.message}`);
});
} catch (error) {
console.error(`[MarketRanking] persist unfolded state failed: ${JSON.stringify(error)}`);
}
}
private initUnfoldedState(): void {
try {
const store: preferences.Preferences | undefined = this.getPreferenceStore();
if (store === undefined) {
this.unfolded = false;
return;
}
this.unfolded =
store.getSync(SP_KEY_FIRST_PAGE_MARKET_RANKING_EXPAND, false) as boolean;
} catch (error) {
console.error(`[MarketRanking] read unfolded state failed: ${JSON.stringify(error)}`);
this.unfolded = false;
}
}
private getPreferenceStore(): preferences.Preferences | undefined {
try {
const context: common.UIAbilityContext =
this.getUIContext().getHostContext() as common.UIAbilityContext;
return preferences.getPreferencesSync(context, {
name: SP_FILE_NAME_FIRST_PAGE_MARKET_RANKING,
});
} catch (error) {
console.error(`[MarketRanking] get preference store failed: ${JSON.stringify(error)}`);
return undefined;
}
} }
private showTabGradient(): boolean { private showTabGradient(): boolean {
@@ -612,9 +654,14 @@ export struct MarketRankingNodeComponent {
} }
private priceColor(item: RankItem): Resource { private priceColor(item: RankItem): Resource {
if (item.price === undefined || item.price_chg === undefined) { if (item.price_chg === undefined) {
return $r('app.color.text_primary'); return $r('app.color.text_primary');
} }
return riseFallColor(item.price_chg); if (item.price_chg > 0) {
return $r('app.color.color_rise');
} else if (item.price_chg < 0) {
return $r('app.color.color_fall');
}
return $r('app.color.text_primary');
} }
} }
@@ -16,7 +16,6 @@
{ "name": "color_fall", "value": "#07AB4B" }, { "name": "color_fall", "value": "#07AB4B" },
{ "name": "color_orange", "value": "#FF661A" }, { "name": "color_orange", "value": "#FF661A" },
{ "name": "bg_orange", "value": "#1AFF661A" }, { "name": "bg_orange", "value": "#1AFF661A" },
{ "name": "transparent_white", "value": "#00FFFFFF" },
{ "name": "tab_gradient_start", "value": "#00FFFFFF" } { "name": "tab_gradient_start", "value": "#00FFFFFF" }
] ]
} }
@@ -0,0 +1,24 @@
{
"string": [
{
"name": "ai_pick_empty_image_light_url",
"value": "https://u.thsi.cn/imgsrc/bbs/8eee9cd4fa7e359032de11a3f7c2a70b_300_230.png"
},
{
"name": "ai_pick_empty_image_dark_url",
"value": "https://u.thsi.cn/imgsrc/bbs/1c4e679244ed87231befad2b04cddfe2_300_230.png"
},
{
"name": "ai_pick_home_url",
"value": "https://fupage.10jqka.com.cn/ai-web/ai-diagnosis-home.html?sync=1"
},
{
"name": "ai_pick_strategy_detail_url",
"value": "https://fupage.10jqka.com.cn/ai-web/ai-diagnosis-detail.html"
},
{
"name": "ai_pick_strategy_label_url",
"value": "https://fupage.10jqka.com.cn/ai-web/ai-diagnosis-label.html"
}
]
}