Compare commits
2
Commits
888f7e316c
...
2a3b09236e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a3b09236e | ||
|
|
7f38efa220 |
@@ -32,5 +32,6 @@ HTTP 只负责返回 contracts;最新价、涨跌幅、成交额等动态字
|
|||||||
|
|
||||||
当前 HTTP 和行情连接均为 Mock。接入真实环境时,主要替换 `MarketRankingDataFetcher`
|
当前 HTTP 和行情连接均为 Mock。接入真实环境时,主要替换 `MarketRankingDataFetcher`
|
||||||
和 `MarketRankingHqRequestClient` 的内部实现,视图层数据结构和生命周期保持不变。
|
和 `MarketRankingHqRequestClient` 的内部实现,视图层数据结构和生命周期保持不变。
|
||||||
|
真实 HTTP 接入后还应增加请求版本校验或取消机制,避免快速切换 Tab 时较早的响应覆盖当前状态。
|
||||||
|
|
||||||
具体 HTTP 与 4106 参数见 [市场排名接口文档](market-ranking-interfaces.md)。
|
具体 HTTP 与 4106 参数见 [市场排名接口文档](market-ranking-interfaces.md)。
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
EMPTY_IMAGE_LIGHT,
|
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,
|
||||||
@@ -29,6 +30,18 @@ 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 {
|
||||||
// 当前卡片配置。
|
// 当前卡片配置。
|
||||||
@@ -142,31 +155,32 @@ export struct AiPickNodeComponent {
|
|||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
CardTitle() {
|
CardTitle() {
|
||||||
Row({ space: 4 }) {
|
Row() {
|
||||||
Text(this.cardConfig?.card_title.value || $r('app.string.ai_pick_title'))
|
Text(this.cardConfig?.card_title.value || $r('app.string.ai_pick_title'))
|
||||||
.fontSize(18)
|
.fontSize(18)
|
||||||
.fontWeight(FontWeight.Bold)
|
.fontWeight(500)
|
||||||
.fontColor($r('app.color.text_primary'))
|
.fontColor($r('app.color.text_primary'))
|
||||||
.maxLines(1)
|
.maxLines(1)
|
||||||
|
|
||||||
if ((this.cardConfig?.card_url.android ?? '') !== '' ||
|
if ((this.cardConfig?.card_url.android ?? '') !== '' ||
|
||||||
(this.cardConfig?.card_url.ios ?? '') !== '') {
|
(this.cardConfig?.card_url.ios ?? '') !== '') {
|
||||||
Image(this.isDarkMode ? TITLE_ARROW_DARK : TITLE_ARROW_LIGHT)
|
Image(this.isDarkMode ? TITLE_ARROW_DARK : TITLE_ARROW_LIGHT)
|
||||||
.width(12)
|
.width(20)
|
||||||
.height(12)
|
.height(20)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((this.cardConfig?.explain_message ?? '') !== '') {
|
if ((this.cardConfig?.explain_message ?? '') !== '') {
|
||||||
Image(this.isDarkMode ? INFO_IMAGE_DARK : INFO_IMAGE_LIGHT)
|
Image(this.isDarkMode ? INFO_IMAGE_DARK : INFO_IMAGE_LIGHT)
|
||||||
.width(16)
|
.width(16)
|
||||||
.height(16)
|
.height(16)
|
||||||
|
.margin({ left: 8 })
|
||||||
.onClick((event: ClickEvent) => {
|
.onClick((event: ClickEvent) => {
|
||||||
this.showExplainSheet = true;
|
this.showExplainSheet = true;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height(48)
|
.height(CARD_TITLE_HEIGHT)
|
||||||
.alignItems(VerticalAlign.Center)
|
.alignItems(VerticalAlign.Center)
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
this.jumpToCardTitle();
|
this.jumpToCardTitle();
|
||||||
@@ -232,16 +246,18 @@ export struct AiPickNodeComponent {
|
|||||||
if (this.cardData.tabs[$$.tabIdx].strategy.tagList.length > 0) {
|
if (this.cardData.tabs[$$.tabIdx].strategy.tagList.length > 0) {
|
||||||
Row({ space: 4 }) {
|
Row({ space: 4 }) {
|
||||||
ForEach(this.cardData.tabs[$$.tabIdx].strategy.tagList, (tag: string) => {
|
ForEach(this.cardData.tabs[$$.tabIdx].strategy.tagList, (tag: string) => {
|
||||||
Text(tag)
|
Row() {
|
||||||
.fontSize(11)
|
Text(tag)
|
||||||
.lineHeight(18)
|
.fontSize(11)
|
||||||
.fontColor($r('app.color.text_blue'))
|
.fontColor($r('app.color.text_blue'))
|
||||||
.maxLines(1)
|
.maxLines(1)
|
||||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||||
|
}
|
||||||
.height(18)
|
.height(18)
|
||||||
.padding({ left: 3, right: 3 })
|
.padding({ left: 3, right: 3 })
|
||||||
.borderRadius(2)
|
.borderRadius(2)
|
||||||
.border({ width: 0.5, color: $r('app.color.text_blue') })
|
.border({ width: 0.5, color: $r('app.color.text_blue') })
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
this.jumpToLabel(
|
this.jumpToLabel(
|
||||||
this.cardData.tabs[$$.tabIdx].strategy,
|
this.cardData.tabs[$$.tabIdx].strategy,
|
||||||
@@ -255,7 +271,6 @@ export struct AiPickNodeComponent {
|
|||||||
}
|
}
|
||||||
.layoutWeight(1)
|
.layoutWeight(1)
|
||||||
.alignItems(HorizontalAlign.Start)
|
.alignItems(HorizontalAlign.Start)
|
||||||
.margin({ bottom: 10 })
|
|
||||||
|
|
||||||
// 右侧:策略图片(仅系统策略有图,右侧 image)
|
// 右侧:策略图片(仅系统策略有图,右侧 image)
|
||||||
// imgs 长度>0 且当前选中位(imgs[0],暂无主题切换)非空才渲染
|
// imgs 长度>0 且当前选中位(imgs[0],暂无主题切换)非空才渲染
|
||||||
@@ -268,7 +283,8 @@ export struct AiPickNodeComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.margin({ top: 10 })
|
.height(STRATEGY_DESC_HEIGHT)
|
||||||
|
.padding({ top: 10, bottom: 10 })
|
||||||
.alignItems(VerticalAlign.Top)
|
.alignItems(VerticalAlign.Top)
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
this.jumpToStrategyDetail(
|
this.jumpToStrategyDetail(
|
||||||
@@ -285,11 +301,11 @@ export struct AiPickNodeComponent {
|
|||||||
|
|
||||||
Column() {
|
Column() {
|
||||||
Text($r('app.string.ai_pick_loading'))
|
Text($r('app.string.ai_pick_loading'))
|
||||||
.fontSize(16)
|
.fontSize(14)
|
||||||
.fontColor($r('app.color.text_grey'))
|
.fontColor($r('app.color.text_grey'))
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height(240)
|
.height(MAIN_CONTENT_HEIGHT)
|
||||||
.alignItems(HorizontalAlign.Center)
|
.alignItems(HorizontalAlign.Center)
|
||||||
.justifyContent(FlexAlign.Center)
|
.justifyContent(FlexAlign.Center)
|
||||||
} else {
|
} else {
|
||||||
@@ -297,6 +313,9 @@ export struct AiPickNodeComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
|
.backgroundColor($r('app.color.card_bg'))
|
||||||
|
.borderRadius(4)
|
||||||
|
.padding({ left: 10, right: 10, bottom: 10 })
|
||||||
.bindSheet(this.showExplainSheet, this.ExplainSheet, {
|
.bindSheet(this.showExplainSheet, this.ExplainSheet, {
|
||||||
height: SheetSize.FIT_CONTENT,
|
height: SheetSize.FIT_CONTENT,
|
||||||
dragBar: true,
|
dragBar: true,
|
||||||
@@ -325,14 +344,15 @@ export struct AiPickNodeComponent {
|
|||||||
ForEach(this.cardData.tabs, (tab: PickTabData, index: number) => {
|
ForEach(this.cardData.tabs, (tab: PickTabData, index: number) => {
|
||||||
Text(`${tab.strategy.name}(${tab.strategy.count})`)
|
Text(`${tab.strategy.name}(${tab.strategy.count})`)
|
||||||
.fontSize(14)
|
.fontSize(14)
|
||||||
.fontWeight(this.tabIdx === index ? FontWeight.Bold : FontWeight.Normal)
|
.fontWeight(this.tabIdx === index ? FontWeight.Medium : FontWeight.Normal)
|
||||||
.fontColor(this.tabIdx === index
|
.fontColor(this.tabIdx === index
|
||||||
? $r('app.color.text_blue')
|
? $r('app.color.text_blue')
|
||||||
: $r('app.color.text_secondary'))
|
: $r('app.color.text_secondary'))
|
||||||
.constraintSize({ minWidth: 63 })
|
.constraintSize({ minWidth: 66 })
|
||||||
.maxLines(1)
|
.maxLines(1)
|
||||||
.textAlign(TextAlign.Center)
|
.textAlign(TextAlign.Center)
|
||||||
.padding({ top: 6, bottom: 6, left: 12, right: 12 })
|
.height(TAB_BAR_HEIGHT)
|
||||||
|
.padding({ left: 8, right: 8 })
|
||||||
.borderRadius(4)
|
.borderRadius(4)
|
||||||
.backgroundColor(this.tabIdx === index
|
.backgroundColor(this.tabIdx === index
|
||||||
? $r('app.color.pill_active_bg')
|
? $r('app.color.pill_active_bg')
|
||||||
@@ -347,7 +367,8 @@ export struct AiPickNodeComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height(30)
|
.height(TAB_BAR_HEIGHT)
|
||||||
|
.padding({ right: 10 })
|
||||||
.scrollable(ScrollDirection.Horizontal)
|
.scrollable(ScrollDirection.Horizontal)
|
||||||
.scrollBar(BarState.Off)
|
.scrollBar(BarState.Off)
|
||||||
.align(Alignment.Start)
|
.align(Alignment.Start)
|
||||||
@@ -358,16 +379,17 @@ export struct AiPickNodeComponent {
|
|||||||
if (this.showTabGradient()) {
|
if (this.showTabGradient()) {
|
||||||
Row()
|
Row()
|
||||||
.width(24)
|
.width(24)
|
||||||
.height(30)
|
.height(TAB_BAR_HEIGHT)
|
||||||
.linearGradient({
|
.linearGradient({
|
||||||
angle: 90,
|
angle: 90,
|
||||||
colors: [['#00FFFFFF', 0], [$r('app.color.card_bg'), 1]],
|
colors: [[$r('app.color.tab_gradient_start'), 0],
|
||||||
|
[$r('app.color.card_bg'), 1]],
|
||||||
})
|
})
|
||||||
.hitTestBehavior(HitTestMode.None)
|
.hitTestBehavior(HitTestMode.None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height(30)
|
.height(TAB_BAR_HEIGHT)
|
||||||
.onAreaChange((oldValue: Area, newValue: Area) => {
|
.onAreaChange((oldValue: Area, newValue: Area) => {
|
||||||
this.tabViewportWidth = Number(newValue.width);
|
this.tabViewportWidth = Number(newValue.width);
|
||||||
})
|
})
|
||||||
@@ -380,97 +402,102 @@ export struct AiPickNodeComponent {
|
|||||||
if (this.cardData.tabs[this.tabIdx].items.length === 0) {
|
if (this.cardData.tabs[this.tabIdx].items.length === 0) {
|
||||||
this.EmptyState($r('app.string.ai_pick_no_contract'))
|
this.EmptyState($r('app.string.ai_pick_no_contract'))
|
||||||
} else {
|
} else {
|
||||||
// ── 表头 ──────────────────────────────────────────────
|
Column() {
|
||||||
Row() {
|
// ── 表头 ────────────────────────────────────────────
|
||||||
Text(formatColName(this.cardData.tabs[this.tabIdx].keysList[0]))
|
Row({ space: 8 }) {
|
||||||
.fontSize(12)
|
Text(formatColName(this.cardData.tabs[this.tabIdx].keysList[0]))
|
||||||
.fontColor($r('app.color.text_tertiary'))
|
.fontSize(14)
|
||||||
.width('35%')
|
.fontColor($r('app.color.text_tertiary'))
|
||||||
Text(formatColName(this.cardData.tabs[this.tabIdx].keysList[1]))
|
.width('35%')
|
||||||
.fontSize(12)
|
Text(formatColName(this.cardData.tabs[this.tabIdx].keysList[1]))
|
||||||
.fontColor($r('app.color.text_tertiary'))
|
.fontSize(14)
|
||||||
.layoutWeight(1)
|
.fontColor($r('app.color.text_tertiary'))
|
||||||
.margin({ left: 8 })
|
.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(12)
|
.fontSize(14)
|
||||||
.fontColor($r('app.color.text_tertiary'))
|
.fontColor($r('app.color.text_tertiary'))
|
||||||
.layoutWeight(1)
|
.layoutWeight(1)
|
||||||
.margin({ left: 8 })
|
.textAlign(TextAlign.End)
|
||||||
.textAlign(TextAlign.End)
|
}
|
||||||
}
|
.width('100%')
|
||||||
.width('100%')
|
.height(TABLE_HEADER_HEIGHT)
|
||||||
.height(24)
|
|
||||||
.margin({ top: 8 })
|
|
||||||
.padding({ bottom: 2 })
|
|
||||||
.alignItems(VerticalAlign.Center)
|
|
||||||
.border({ width: { bottom: 0.5 }, color: $r('app.color.divider_color') })
|
|
||||||
|
|
||||||
// ── 合约列表 ──────────────────────────────────────────
|
// ── 合约列表 ────────────────────────────────────────
|
||||||
ForEach(
|
ForEach(
|
||||||
this.cardData.tabs[this.tabIdx].items,
|
this.cardData.tabs[this.tabIdx].items,
|
||||||
(item: PickContractItem) => {
|
(item: PickContractItem) => {
|
||||||
Row() {
|
Row({ space: 8 }) {
|
||||||
Row({ space: 5 }) {
|
Row({ space: 5 }) {
|
||||||
Text(item.name)
|
Text(item.name)
|
||||||
|
.fontSize(16)
|
||||||
|
.maxFontSize(16)
|
||||||
|
.minFontSize(9)
|
||||||
|
.fontColor($r('app.color.text_primary'))
|
||||||
|
.layoutWeight(1)
|
||||||
|
.maxLines(1)
|
||||||
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||||
|
if (item.isMain) {
|
||||||
|
Text($r('app.string.ai_pick_main_contract'))
|
||||||
|
.fontSize(11)
|
||||||
|
.fontColor($r('app.color.color_orange'))
|
||||||
|
.width(16)
|
||||||
|
.height(16)
|
||||||
|
.textAlign(TextAlign.Center)
|
||||||
|
.borderRadius(2)
|
||||||
|
.backgroundColor($r('app.color.bg_orange'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.width('35%')
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
|
||||||
|
Text(cellValue(
|
||||||
|
this.cardData.tabs[this.tabIdx].keysList[1],
|
||||||
|
item,
|
||||||
|
this.cardData.tabs[this.tabIdx].fieldList,
|
||||||
|
))
|
||||||
.fontSize(16)
|
.fontSize(16)
|
||||||
.fontColor($r('app.color.text_primary'))
|
.maxFontSize(16)
|
||||||
|
.minFontSize(9)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontFamily('monospace')
|
||||||
|
.fontColor(cellColor(this.cardData.tabs[this.tabIdx].keysList[1], item))
|
||||||
|
.layoutWeight(1)
|
||||||
|
.textAlign(TextAlign.End)
|
||||||
|
.maxLines(1)
|
||||||
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||||
|
|
||||||
|
Text(cellValue(
|
||||||
|
this.cardData.tabs[this.tabIdx].keysList[2],
|
||||||
|
item,
|
||||||
|
this.cardData.tabs[this.tabIdx].fieldList,
|
||||||
|
))
|
||||||
|
.fontSize(16)
|
||||||
|
.maxFontSize(16)
|
||||||
|
.minFontSize(9)
|
||||||
|
.fontWeight(500)
|
||||||
|
.fontFamily('monospace')
|
||||||
|
.fontColor(cellColor(this.cardData.tabs[this.tabIdx].keysList[2], item))
|
||||||
|
.layoutWeight(1)
|
||||||
|
.textAlign(TextAlign.End)
|
||||||
.maxLines(1)
|
.maxLines(1)
|
||||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||||
if (item.isMain) {
|
|
||||||
Text($r('app.string.ai_pick_main_contract'))
|
|
||||||
.fontSize(11)
|
|
||||||
.fontColor($r('app.color.color_orange'))
|
|
||||||
.width(16)
|
|
||||||
.height(16)
|
|
||||||
.textAlign(TextAlign.Center)
|
|
||||||
.borderRadius(2)
|
|
||||||
.backgroundColor($r('app.color.bg_orange'))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.width('35%')
|
.width('100%')
|
||||||
|
.height(TABLE_ROW_HEIGHT)
|
||||||
.alignItems(VerticalAlign.Center)
|
.alignItems(VerticalAlign.Center)
|
||||||
|
.border({ width: { top: 1 }, color: $r('app.color.divider_color') })
|
||||||
Text(cellValue(
|
.onClick(() => {
|
||||||
this.cardData.tabs[this.tabIdx].keysList[1],
|
this.jumpToDetail(item);
|
||||||
item,
|
})
|
||||||
this.cardData.tabs[this.tabIdx].fieldList,
|
},
|
||||||
))
|
// 行情字段参与 key,确保整体替换 CardData 后列表行同步刷新。
|
||||||
.fontSize(16)
|
(item: PickContractItem) =>
|
||||||
.fontFamily('monospace')
|
`${item.contract}_${item.market}_${item.price ?? ''}_${item.priceChg ?? ''}`,
|
||||||
.fontColor(cellColor(this.cardData.tabs[this.tabIdx].keysList[1], item))
|
)
|
||||||
.layoutWeight(1)
|
}
|
||||||
.margin({ left: 8 })
|
.width('100%')
|
||||||
.textAlign(TextAlign.End)
|
.height(TABLE_HEIGHT)
|
||||||
.maxLines(1)
|
|
||||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
||||||
|
|
||||||
Text(cellValue(
|
|
||||||
this.cardData.tabs[this.tabIdx].keysList[2],
|
|
||||||
item,
|
|
||||||
this.cardData.tabs[this.tabIdx].fieldList,
|
|
||||||
))
|
|
||||||
.fontSize(16)
|
|
||||||
.fontFamily('monospace')
|
|
||||||
.fontColor(cellColor(this.cardData.tabs[this.tabIdx].keysList[2], item))
|
|
||||||
.layoutWeight(1)
|
|
||||||
.margin({ left: 8 })
|
|
||||||
.textAlign(TextAlign.End)
|
|
||||||
.maxLines(1)
|
|
||||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
||||||
}
|
|
||||||
.width('100%')
|
|
||||||
.height(44)
|
|
||||||
.alignItems(VerticalAlign.Center)
|
|
||||||
.border({ width: { bottom: 0.5 }, color: $r('app.color.divider_color') })
|
|
||||||
.onClick(() => {
|
|
||||||
this.jumpToDetail(item);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// 行情字段参与 key,确保 price/priceChg 变化后对应列表行重新渲染。
|
|
||||||
(item: PickContractItem) =>
|
|
||||||
`${item.contract}_${item.market}_${item.price ?? ''}_${item.priceChg ?? ''}`,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 一句话定制策略 ──────────────────────────────────────
|
// ── 一句话定制策略 ──────────────────────────────────────
|
||||||
@@ -484,30 +511,30 @@ export struct AiPickNodeComponent {
|
|||||||
.height(14)
|
.height(14)
|
||||||
.margin({ top: 2 })
|
.margin({ top: 2 })
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.justifyContent(FlexAlign.Center)
|
.height(CUSTOM_STRATEGY_HEIGHT)
|
||||||
.alignItems(VerticalAlign.Center)
|
.justifyContent(FlexAlign.Center)
|
||||||
.margin({ top: 6 })
|
.alignItems(VerticalAlign.Center)
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
this.jumpToNewHome();
|
this.jumpToNewHome();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
.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 ? EMPTY_IMAGE_DARK : EMPTY_IMAGE_LIGHT)
|
||||||
.width(120)
|
.width(100)
|
||||||
.height(120)
|
.height(100)
|
||||||
.margin({ bottom: 8 })
|
|
||||||
Text(message)
|
Text(message)
|
||||||
.fontSize(16)
|
.fontSize(14)
|
||||||
.fontColor($r('app.color.text_grey'))
|
.fontColor($r('app.color.text_grey'))
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height(240)
|
.height(NO_CONTRACT_HEIGHT)
|
||||||
.alignItems(HorizontalAlign.Center)
|
.alignItems(HorizontalAlign.Center)
|
||||||
.justifyContent(FlexAlign.Center)
|
.justifyContent(FlexAlign.Center)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,37 @@
|
|||||||
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
|
import { AbilityConstant, Configuration, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
|
||||||
|
import { resourceManager } from '@kit.LocalizationKit';
|
||||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||||
import { window } from '@kit.ArkUI';
|
import { window } from '@kit.ArkUI';
|
||||||
|
|
||||||
const DOMAIN = 0x0000;
|
const DOMAIN = 0x0000;
|
||||||
|
|
||||||
export default class EntryAbility extends UIAbility {
|
export default class EntryAbility extends UIAbility {
|
||||||
|
private updateDarkMode(isDarkMode: boolean): void {
|
||||||
|
AppStorage.setOrCreate<boolean>('enableDarkMode', isDarkMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private syncDarkModeFromResources(): void {
|
||||||
|
const configuration: resourceManager.Configuration =
|
||||||
|
this.context.resourceManager.getConfigurationSync();
|
||||||
|
this.updateDarkMode(configuration.colorMode === resourceManager.ColorMode.DARK);
|
||||||
|
}
|
||||||
|
|
||||||
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
|
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
|
||||||
try {
|
try {
|
||||||
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
|
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
|
hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
|
||||||
}
|
}
|
||||||
|
this.syncDarkModeFromResources();
|
||||||
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
|
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onConfigurationUpdate(newConfig: Configuration): void {
|
||||||
|
if (newConfig.colorMode !== undefined) {
|
||||||
|
this.updateDarkMode(newConfig.colorMode === ConfigurationConstant.ColorMode.COLOR_MODE_DARK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onDestroy(): void {
|
onDestroy(): void {
|
||||||
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
|
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
|
||||||
}
|
}
|
||||||
@@ -37,7 +55,7 @@ export default class EntryAbility extends UIAbility {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onForeground(): void {
|
onForeground(): void {
|
||||||
// Ability has brought to foreground
|
this.syncDarkModeFromResources();
|
||||||
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
|
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
CardData,
|
||||||
|
RankItem,
|
||||||
|
RecommendFuturesItem,
|
||||||
|
} from './MarketRankingModels';
|
||||||
|
|
||||||
|
export function cardDataToContracts(cardData: CardData): RecommendFuturesItem[] {
|
||||||
|
return cardData.tableList.map((item: RankItem): RecommendFuturesItem => ({
|
||||||
|
contract_code: item.code,
|
||||||
|
contract_name: item.name,
|
||||||
|
market: item.market,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function contractsToPlaceholderCardData(contracts: RecommendFuturesItem[]): CardData {
|
||||||
|
const cardData: CardData = {
|
||||||
|
tableList: contracts.map((contract: RecommendFuturesItem): RankItem => ({
|
||||||
|
code: contract.contract_code,
|
||||||
|
market: contract.market,
|
||||||
|
name: contract.contract_name,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
return cardData;
|
||||||
|
}
|
||||||
@@ -202,7 +202,7 @@ export class MarketRankingHqRequestClient {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
if (typeof value === 'string' && value !== '') {
|
if (typeof value === 'string' && value !== '') {
|
||||||
const result = Number(value);
|
const result = parseFloat(value);
|
||||||
return Number.isNaN(result) ? undefined : result;
|
return Number.isNaN(result) ? undefined : result;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import {
|
|||||||
RankItem, RecommendFuturesItem, TabMetric,
|
RankItem, RecommendFuturesItem, TabMetric,
|
||||||
} from './MarketRankingModels';
|
} from './MarketRankingModels';
|
||||||
import { MarketRankingDataFetcher } from './MarketRankingDataFetcher';
|
import { MarketRankingDataFetcher } from './MarketRankingDataFetcher';
|
||||||
|
import {
|
||||||
|
cardDataToContracts,
|
||||||
|
contractsToPlaceholderCardData,
|
||||||
|
} from './MarketRankingDataMapper';
|
||||||
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';
|
||||||
@@ -24,15 +28,21 @@ import {
|
|||||||
|
|
||||||
const NORMAL_SIZE = 3;
|
const NORMAL_SIZE = 3;
|
||||||
const MAX_SIZE = 5;
|
const MAX_SIZE = 5;
|
||||||
|
const UNFOLDED_STORAGE_KEY = 'firstPageMarketRankingUnfolded';
|
||||||
|
|
||||||
|
PersistentStorage.persistProp<boolean>(UNFOLDED_STORAGE_KEY, false);
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export struct MarketRankingNodeComponent {
|
export struct MarketRankingNodeComponent {
|
||||||
@State cardConfig: AllCardsCard | undefined = undefined;
|
@State cardConfig: AllCardsCard | undefined = undefined;
|
||||||
@State cardData: CardData = { tableList: [] };
|
@State cardData: CardData = { tableList: [] };
|
||||||
|
private contracts: RecommendFuturesItem[] = [];
|
||||||
|
private tabDataCache: Map<string, CardData> = new Map<string, CardData>();
|
||||||
|
private dataRequestVersion: number = 0;
|
||||||
// Tab 选中/展开状态是纯 UI 交互状态,留在 View 里
|
// Tab 选中/展开状态是纯 UI 交互状态,留在 View 里
|
||||||
@State primaryIdx: number = 0;
|
@State primaryIdx: number = 0;
|
||||||
@State secondaryIdx: number = 0;
|
@State secondaryIdx: number = 0;
|
||||||
@State unfolded: boolean = false;
|
@StorageLink(UNFOLDED_STORAGE_KEY) 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;
|
||||||
@@ -58,6 +68,7 @@ export struct MarketRankingNodeComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
aboutToDisappear(): void {
|
aboutToDisappear(): void {
|
||||||
|
this.dataRequestVersion += 1;
|
||||||
emitter.off(EVENT_TCP_RECONNECT, this.tcpReconnectListener);
|
emitter.off(EVENT_TCP_RECONNECT, this.tcpReconnectListener);
|
||||||
this.stopSubscribeHq();
|
this.stopSubscribeHq();
|
||||||
}
|
}
|
||||||
@@ -100,50 +111,61 @@ export struct MarketRankingNodeComponent {
|
|||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
CardTitle() {
|
CardTitle() {
|
||||||
Row({ space: 4 }) {
|
Row() {
|
||||||
Text(this.cardConfig?.card_title.value || $r('app.string.market_ranking_title'))
|
Row() {
|
||||||
.fontSize(18)
|
Text(this.cardConfig?.card_title.value || $r('app.string.market_ranking_title'))
|
||||||
.fontWeight(FontWeight.Bold)
|
.fontSize(18)
|
||||||
.fontColor($r('app.color.text_primary'))
|
.fontWeight(500)
|
||||||
.maxLines(1)
|
.fontColor($r('app.color.text_primary'))
|
||||||
|
.maxLines(1)
|
||||||
|
|
||||||
if ((this.cardConfig?.card_url.android ?? '') !== '' ||
|
if ((this.cardConfig?.card_url.android ?? '') !== '' ||
|
||||||
(this.cardConfig?.card_url.ios ?? '') !== '') {
|
(this.cardConfig?.card_url.ios ?? '') !== '') {
|
||||||
Image(this.isDarkMode ? TITLE_ARROW_DARK : TITLE_ARROW_LIGHT)
|
Image(this.isDarkMode ? TITLE_ARROW_DARK : TITLE_ARROW_LIGHT)
|
||||||
.width(12)
|
.width(20)
|
||||||
.height(12)
|
.height(20)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this.cardConfig?.explain_message ?? '') !== '') {
|
||||||
|
Image(this.isDarkMode ? INFO_IMAGE_DARK : INFO_IMAGE_LIGHT)
|
||||||
|
.width(16)
|
||||||
|
.height(16)
|
||||||
|
.margin({ left: 8 })
|
||||||
|
.onClick(() => {
|
||||||
|
this.showExplainSheet = true;
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
.height('100%')
|
||||||
|
.onClick(() => {
|
||||||
|
this.jumpToCardTitle();
|
||||||
|
})
|
||||||
|
|
||||||
if ((this.cardConfig?.explain_message ?? '') !== '') {
|
Row() {
|
||||||
Image(this.isDarkMode ? INFO_IMAGE_DARK : INFO_IMAGE_LIGHT)
|
Text(this.unfolded
|
||||||
.width(16)
|
? $r('app.string.market_ranking_collapse')
|
||||||
|
: $r('app.string.market_ranking_expand'))
|
||||||
|
.fontSize(12)
|
||||||
|
.fontColor($r('app.color.text_tertiary'))
|
||||||
.height(16)
|
.height(16)
|
||||||
.onClick((event: ClickEvent) => {
|
.width('100%')
|
||||||
this.showExplainSheet = true;
|
.textAlign(TextAlign.Center)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
.width(40)
|
||||||
|
.height(20)
|
||||||
|
.borderRadius(10)
|
||||||
|
.backgroundColor($r('app.color.pill_inactive_bg'))
|
||||||
|
.margin({ left: 8 })
|
||||||
|
.onClick(() => {
|
||||||
|
this.toggleUnfolded();
|
||||||
|
})
|
||||||
|
|
||||||
Text(this.unfolded
|
Blank()
|
||||||
? $r('app.string.market_ranking_collapse')
|
|
||||||
: $r('app.string.market_ranking_expand'))
|
|
||||||
.fontSize(12)
|
|
||||||
.fontColor($r('app.color.text_tertiary'))
|
|
||||||
.height(20)
|
|
||||||
.padding({ left: 8, right: 8 })
|
|
||||||
.borderRadius(11)
|
|
||||||
.backgroundColor($r('app.color.pill_inactive_bg'))
|
|
||||||
.textAlign(TextAlign.Center)
|
|
||||||
.margin({ left: 4 })
|
|
||||||
.onClick((event: ClickEvent) => {
|
|
||||||
this.unfolded = !this.unfolded;
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height(48)
|
.height(48)
|
||||||
.alignItems(VerticalAlign.Center)
|
.alignItems(VerticalAlign.Center)
|
||||||
.onClick(() => {
|
|
||||||
this.jumpToCardTitle();
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
@@ -197,18 +219,19 @@ export struct MarketRankingNodeComponent {
|
|||||||
Row({ space: 8 }) {
|
Row({ space: 8 }) {
|
||||||
ForEach(TAB_METRICS, (metric: TabMetric, index: number) => {
|
ForEach(TAB_METRICS, (metric: TabMetric, index: number) => {
|
||||||
Text(metric.label)
|
Text(metric.label)
|
||||||
.fontSize(14)
|
|
||||||
.fontWeight(this.primaryIdx === index ? FontWeight.Bold : FontWeight.Normal)
|
|
||||||
.fontColor(this.primaryIdx === index
|
.fontColor(this.primaryIdx === index
|
||||||
? $r('app.color.text_blue')
|
? $r('app.color.text_blue')
|
||||||
: $r('app.color.text_secondary'))
|
: $r('app.color.text_secondary'))
|
||||||
.constraintSize({ minWidth: 63 })
|
.fontSize(14)
|
||||||
|
.fontWeight(this.primaryIdx === index ? FontWeight.Medium : FontWeight.Normal)
|
||||||
|
.padding({ left: 8, right: 8 })
|
||||||
|
.height(30)
|
||||||
|
.constraintSize({ minWidth: 66 })
|
||||||
.textAlign(TextAlign.Center)
|
.textAlign(TextAlign.Center)
|
||||||
.padding({ top: 6, bottom: 6, left: 12, right: 12 })
|
|
||||||
.borderRadius(4)
|
|
||||||
.backgroundColor(this.primaryIdx === index
|
.backgroundColor(this.primaryIdx === index
|
||||||
? $r('app.color.pill_active_bg')
|
? $r('app.color.pill_active_bg')
|
||||||
: $r('app.color.pill_inactive_bg'))
|
: $r('app.color.pill_inactive_bg'))
|
||||||
|
.borderRadius(4)
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
this.primaryIdx = index;
|
this.primaryIdx = index;
|
||||||
this.secondaryIdx = 0;
|
this.secondaryIdx = 0;
|
||||||
@@ -235,7 +258,7 @@ export struct MarketRankingNodeComponent {
|
|||||||
.height(30)
|
.height(30)
|
||||||
.linearGradient({
|
.linearGradient({
|
||||||
angle: 90,
|
angle: 90,
|
||||||
colors: [['#00FFFFFF', 0], [$r('app.color.card_bg'), 1]],
|
colors: [[$r('app.color.tab_gradient_start'), 0], [$r('app.color.card_bg'), 1]],
|
||||||
})
|
})
|
||||||
.hitTestBehavior(HitTestMode.None)
|
.hitTestBehavior(HitTestMode.None)
|
||||||
}
|
}
|
||||||
@@ -259,7 +282,7 @@ export struct MarketRankingNodeComponent {
|
|||||||
}
|
}
|
||||||
Text(period.label)
|
Text(period.label)
|
||||||
.fontSize(12)
|
.fontSize(12)
|
||||||
.fontWeight(this.secondaryIdx === index ? FontWeight.Bold : FontWeight.Normal)
|
.fontWeight(this.secondaryIdx === index ? FontWeight.Medium : FontWeight.Normal)
|
||||||
.fontColor(this.secondaryIdx === index
|
.fontColor(this.secondaryIdx === index
|
||||||
? $r('app.color.text_primary')
|
? $r('app.color.text_primary')
|
||||||
: $r('app.color.text_tertiary'))
|
: $r('app.color.text_tertiary'))
|
||||||
@@ -272,8 +295,7 @@ export struct MarketRankingNodeComponent {
|
|||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.justifyContent(FlexAlign.End)
|
.justifyContent(FlexAlign.End)
|
||||||
.padding({ left: 10, right: 10 })
|
.margin({ top: 8 })
|
||||||
.margin({ top: 8, left: -10, right: -10 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.cardData.tableList.length === 0) {
|
if (this.cardData.tableList.length === 0) {
|
||||||
@@ -284,98 +306,89 @@ export struct MarketRankingNodeComponent {
|
|||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height(this.emptyHeight())
|
.height(this.emptyHeight())
|
||||||
.margin({ top: 10 })
|
|
||||||
.alignItems(HorizontalAlign.Center)
|
.alignItems(HorizontalAlign.Center)
|
||||||
.justifyContent(FlexAlign.Center)
|
.justifyContent(FlexAlign.Center)
|
||||||
} else {
|
} else {
|
||||||
// 表头
|
Column() {
|
||||||
Row() {
|
// 表头
|
||||||
Text($r('app.string.market_ranking_contract_name'))
|
Row({ space: 8 }) {
|
||||||
.fontSize(12)
|
Text($r('app.string.market_ranking_contract_name'))
|
||||||
.lineHeight(21)
|
.fontSize(14)
|
||||||
.fontColor($r('app.color.text_tertiary'))
|
.fontColor($r('app.color.text_tertiary'))
|
||||||
.width('35%')
|
.width('35%')
|
||||||
Text($r('app.string.market_ranking_latest_price'))
|
Text($r('app.string.market_ranking_latest_price'))
|
||||||
.fontSize(12)
|
.fontSize(14)
|
||||||
.lineHeight(21)
|
.fontColor($r('app.color.text_tertiary'))
|
||||||
.fontColor($r('app.color.text_tertiary'))
|
.layoutWeight(1)
|
||||||
.layoutWeight(1)
|
.textAlign(TextAlign.End)
|
||||||
.margin({ left: 8 })
|
Text(this.thirdColumnTitle())
|
||||||
.textAlign(TextAlign.End)
|
.fontSize(14)
|
||||||
Text(this.thirdColumnTitle())
|
.fontColor($r('app.color.text_tertiary'))
|
||||||
.fontSize(12)
|
.layoutWeight(1)
|
||||||
.lineHeight(21)
|
.textAlign(TextAlign.End)
|
||||||
.fontColor($r('app.color.text_tertiary'))
|
}
|
||||||
.layoutWeight(1)
|
.width('100%')
|
||||||
.margin({ left: 8 })
|
.height(40)
|
||||||
.textAlign(TextAlign.End)
|
|
||||||
|
// 表格列表
|
||||||
|
ForEach(
|
||||||
|
this.cardData.tableList.slice(0, this.displaySize()),
|
||||||
|
(item: RankItem, _index: number) => {
|
||||||
|
Row({ space: 8 }) {
|
||||||
|
Text(item.name)
|
||||||
|
.fontSize(16)
|
||||||
|
.maxFontSize(16)
|
||||||
|
.minFontSize(9)
|
||||||
|
.fontColor($r('app.color.text_primary'))
|
||||||
|
.width('35%')
|
||||||
|
.maxLines(1)
|
||||||
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||||
|
Text(item.price === undefined ? '--' : `${item.price}`)
|
||||||
|
.fontSize(16)
|
||||||
|
.fontWeight(500)
|
||||||
|
.maxFontSize(16)
|
||||||
|
.minFontSize(9)
|
||||||
|
.fontColor(this.priceColor(item))
|
||||||
|
.layoutWeight(1)
|
||||||
|
.textAlign(TextAlign.End)
|
||||||
|
.maxLines(1)
|
||||||
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||||
|
Text(this.thirdColumnValue(item))
|
||||||
|
.fontSize(16)
|
||||||
|
.fontWeight(500)
|
||||||
|
.maxFontSize(16)
|
||||||
|
.minFontSize(9)
|
||||||
|
.fontColor(this.thirdColumnColor(item))
|
||||||
|
.layoutWeight(1)
|
||||||
|
.textAlign(TextAlign.End)
|
||||||
|
.maxLines(1)
|
||||||
|
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height(45)
|
||||||
|
.alignItems(VerticalAlign.Center)
|
||||||
|
.border({ width: { top: 1 }, color: $r('app.color.divider_color') })
|
||||||
|
.onClick(() => {
|
||||||
|
if (item.market !== undefined) {
|
||||||
|
this.jumpToDetail(item);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 行情更新时字段参与 key,确保整体替换 CardData 后列表行同步刷新。
|
||||||
|
(item: RankItem) =>
|
||||||
|
`${item.code}_${item.market}_${item.price ?? ''}_${item.price_chg ?? ''}_` +
|
||||||
|
`${item.chg_speed ?? ''}_${item.turnover ?? ''}_${item.incre_posit ?? ''}`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
.height(23)
|
.height(this.emptyHeight())
|
||||||
.margin({ top: 10 })
|
.animation({ duration: 180, curve: Curve.EaseInOut })
|
||||||
.padding({ bottom: 2 })
|
|
||||||
.alignItems(VerticalAlign.Center)
|
|
||||||
.border({ width: { bottom: 0.5 }, color: $r('app.color.divider_color') })
|
|
||||||
|
|
||||||
// 表格列表
|
|
||||||
ForEach(
|
|
||||||
this.cardData.tableList.slice(0, this.displaySize()),
|
|
||||||
(item: RankItem, index: number) => {
|
|
||||||
Row() {
|
|
||||||
Text(item.name)
|
|
||||||
.fontSize(16)
|
|
||||||
.minFontSize(10)
|
|
||||||
.lineHeight(21)
|
|
||||||
.fontColor($r('app.color.text_primary'))
|
|
||||||
.width('35%')
|
|
||||||
.maxLines(1)
|
|
||||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
||||||
Text(item.price === undefined ? '--' : `${item.price}`)
|
|
||||||
.fontSize(16)
|
|
||||||
.minFontSize(10)
|
|
||||||
.lineHeight(21)
|
|
||||||
.fontFamily('monospace')
|
|
||||||
.fontColor(this.priceColor(item))
|
|
||||||
.layoutWeight(1)
|
|
||||||
.margin({ left: 8 })
|
|
||||||
.textAlign(TextAlign.End)
|
|
||||||
.maxLines(1)
|
|
||||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
||||||
Text(this.thirdColumnValue(item))
|
|
||||||
.fontSize(16)
|
|
||||||
.minFontSize(10)
|
|
||||||
.lineHeight(21)
|
|
||||||
.fontFamily('monospace')
|
|
||||||
.fontColor(this.thirdColumnColor(item))
|
|
||||||
.layoutWeight(1)
|
|
||||||
.margin({ left: 8 })
|
|
||||||
.textAlign(TextAlign.End)
|
|
||||||
.maxLines(1)
|
|
||||||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
|
||||||
}
|
|
||||||
.width('100%')
|
|
||||||
.height(45)
|
|
||||||
.alignItems(VerticalAlign.Center)
|
|
||||||
.border(index === Math.min(
|
|
||||||
this.displaySize(),
|
|
||||||
this.cardData.tableList.length,
|
|
||||||
) - 1
|
|
||||||
? undefined
|
|
||||||
: { width: { bottom: 0.5 }, color: $r('app.color.divider_color') })
|
|
||||||
.onClick(() => {
|
|
||||||
if (item.market !== undefined) {
|
|
||||||
this.jumpToDetail(item);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// 行情更新时字段参与 key,确保整体替换 CardData 后列表行同步刷新。
|
|
||||||
(item: RankItem) =>
|
|
||||||
`${item.code}_${item.market}_${item.price ?? ''}_${item.price_chg ?? ''}_` +
|
|
||||||
`${item.chg_speed ?? ''}_${item.turnover ?? ''}_${item.incre_posit ?? ''}`,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
|
.backgroundColor($r('app.color.card_bg'))
|
||||||
|
.borderRadius(4)
|
||||||
|
.padding({ left: 10, right: 10, bottom: 10 })
|
||||||
.bindSheet(this.showExplainSheet, this.ExplainSheet, {
|
.bindSheet(this.showExplainSheet, this.ExplainSheet, {
|
||||||
height: SheetSize.FIT_CONTENT,
|
height: SheetSize.FIT_CONTENT,
|
||||||
dragBar: true,
|
dragBar: true,
|
||||||
@@ -393,40 +406,61 @@ export struct MarketRankingNodeComponent {
|
|||||||
|
|
||||||
// 先请求合约榜单填充列表,再根据当前 CardData 订阅行情。
|
// 先请求合约榜单填充列表,再根据当前 CardData 订阅行情。
|
||||||
private async updateCardData(primaryIdx: number, secondaryIdx: number): Promise<void> {
|
private async updateCardData(primaryIdx: number, secondaryIdx: number): Promise<void> {
|
||||||
// 切换 Tab 后立即清理旧列表,避免新请求返回前闪现上一 Tab 的数据。
|
const requestVersion: number = this.dataRequestVersion + 1;
|
||||||
|
this.dataRequestVersion = requestVersion;
|
||||||
|
const cacheKey: string = this.getTabCacheKey(primaryIdx, secondaryIdx);
|
||||||
|
const cachedData: CardData | undefined = this.tabDataCache.get(cacheKey);
|
||||||
this.stopSubscribeHq();
|
this.stopSubscribeHq();
|
||||||
this.cardData = { tableList: [] };
|
if (cachedData !== undefined) {
|
||||||
|
this.cardData = cachedData;
|
||||||
|
this.contracts = cardDataToContracts(cachedData);
|
||||||
|
if (this.firstPageVisible) {
|
||||||
|
this.subscribeHq(primaryIdx, secondaryIdx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.contracts = [];
|
||||||
|
this.cardData = { tableList: [] };
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const contracts = await MarketRankingDataFetcher
|
const contracts = await MarketRankingDataFetcher
|
||||||
.getInstance()
|
.getInstance()
|
||||||
.fetchRecommendFutures(primaryIdx, secondaryIdx);
|
.fetchRecommendFutures(primaryIdx, secondaryIdx);
|
||||||
this.cardData = {
|
if (!this.isCurrentDataRequest(requestVersion, primaryIdx, secondaryIdx)) {
|
||||||
tableList: contracts.map((contract: RecommendFuturesItem): RankItem => ({
|
return;
|
||||||
code: contract.contract_code,
|
}
|
||||||
market: contract.market,
|
this.contracts = contracts;
|
||||||
name: contract.contract_name,
|
if (cachedData === undefined) {
|
||||||
})),
|
this.cardData = contractsToPlaceholderCardData(contracts);
|
||||||
};
|
}
|
||||||
|
this.tabDataCache.set(cacheKey, this.cardData);
|
||||||
if (this.firstPageVisible) {
|
if (this.firstPageVisible) {
|
||||||
this.subscribeHq(primaryIdx, secondaryIdx);
|
this.subscribeHq(primaryIdx, secondaryIdx);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
this.cardData = { tableList: [] };
|
if (!this.isCurrentDataRequest(requestVersion, primaryIdx, secondaryIdx)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cachedData === undefined) {
|
||||||
|
this.contracts = [];
|
||||||
|
this.cardData = { tableList: [] };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用当前 CardData 中的合约列表订阅行情,推送后整体更新 CardData。
|
// 使用 HTTP 保存的合约列表订阅行情,推送后整体更新当前 Tab 缓存。
|
||||||
private subscribeHq(primaryIdx: number, secondaryIdx: number): void {
|
private subscribeHq(primaryIdx: number, secondaryIdx: number): void {
|
||||||
const contracts = this.cardData.tableList.map((item: RankItem): RecommendFuturesItem => ({
|
this.stopSubscribeHq();
|
||||||
contract_code: item.code,
|
if (this.contracts.length === 0) {
|
||||||
contract_name: item.name,
|
return;
|
||||||
market: item.market,
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
this.hqUnsubscribe = MarketRankingHqRequestClient
|
this.hqUnsubscribe = MarketRankingHqRequestClient
|
||||||
.getInstance()
|
.getInstance()
|
||||||
.subscribeHq(contracts, primaryIdx, secondaryIdx, (cardData: CardData): void => {
|
.subscribeHq(this.contracts, primaryIdx, secondaryIdx, (cardData: CardData): void => {
|
||||||
this.cardData = cardData;
|
if (primaryIdx === this.primaryIdx && secondaryIdx === this.secondaryIdx) {
|
||||||
|
this.cardData = cardData;
|
||||||
|
this.tabDataCache.set(this.getTabCacheKey(primaryIdx, secondaryIdx), cardData);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,6 +471,24 @@ export struct MarketRankingNodeComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isCurrentDataRequest(
|
||||||
|
requestVersion: number,
|
||||||
|
primaryIdx: number,
|
||||||
|
secondaryIdx: number,
|
||||||
|
): boolean {
|
||||||
|
return requestVersion === this.dataRequestVersion &&
|
||||||
|
primaryIdx === this.primaryIdx &&
|
||||||
|
secondaryIdx === this.secondaryIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTabCacheKey(primaryIdx: number, secondaryIdx: number): string {
|
||||||
|
const metric: TabMetric = TAB_METRICS[primaryIdx];
|
||||||
|
if (metric.hasChildren) {
|
||||||
|
return `${metric.id}_${PERIOD_RANGES[secondaryIdx].id}`;
|
||||||
|
}
|
||||||
|
return metric.id;
|
||||||
|
}
|
||||||
|
|
||||||
// 跳转到分时详情页
|
// 跳转到分时详情页
|
||||||
// todo: 后续跳转为真实的页面
|
// todo: 后续跳转为真实的页面
|
||||||
private jumpToDetail(item: RankItem): void {
|
private jumpToDetail(item: RankItem): void {
|
||||||
@@ -473,6 +525,10 @@ export struct MarketRankingNodeComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 以下是UI相关
|
// 以下是UI相关
|
||||||
|
private toggleUnfolded(): void {
|
||||||
|
this.unfolded = !this.unfolded;
|
||||||
|
}
|
||||||
|
|
||||||
private showTabGradient(): boolean {
|
private showTabGradient(): boolean {
|
||||||
const maxOffset = this.tabContentWidth - this.tabViewportWidth;
|
const maxOffset = this.tabContentWidth - this.tabViewportWidth;
|
||||||
return maxOffset > 0 && this.tabScrollOffset < maxOffset;
|
return maxOffset > 0 && this.tabScrollOffset < maxOffset;
|
||||||
@@ -480,7 +536,7 @@ export struct MarketRankingNodeComponent {
|
|||||||
|
|
||||||
private emptyHeight(): number {
|
private emptyHeight(): number {
|
||||||
const rowHeight = 45;
|
const rowHeight = 45;
|
||||||
const headerHeight = 24;
|
const headerHeight = 40;
|
||||||
return this.displaySize() * rowHeight + headerHeight;
|
return this.displaySize() * rowHeight + headerHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
{ "name": "color_rise", "value": "#FF2436" },
|
{ "name": "color_rise", "value": "#FF2436" },
|
||||||
{ "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" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
{ "name": "pill_active_bg", "value": "#1A527DFF" },
|
{ "name": "pill_active_bg", "value": "#1A527DFF" },
|
||||||
{ "name": "pill_inactive_bg", "value": "#0FFFFFFF" },
|
{ "name": "pill_inactive_bg", "value": "#0FFFFFFF" },
|
||||||
{ "name": "color_rise", "value": "#FF2436" },
|
{ "name": "color_rise", "value": "#FF2436" },
|
||||||
{ "name": "color_fall", "value": "#07AB4B" }
|
{ "name": "color_fall", "value": "#07AB4B" },
|
||||||
|
{ "name": "tab_gradient_start", "value": "#002B2B2B" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user