feat(cards): 完善 AI Pick 样式和深色主题

This commit is contained in:
clz
2026-07-27 17:04:26 +08:00
parent 7f38efa220
commit 2a3b09236e
5 changed files with 172 additions and 125 deletions
@@ -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) => {
Row() {
Text(tag) Text(tag)
.fontSize(11) .fontSize(11)
.lineHeight(18)
.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,41 +402,39 @@ 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() { // ── 表头 ────────────────────────────────────────────
Row({ space: 8 }) {
Text(formatColName(this.cardData.tabs[this.tabIdx].keysList[0])) Text(formatColName(this.cardData.tabs[this.tabIdx].keysList[0]))
.fontSize(12) .fontSize(14)
.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(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)
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(24) .height(TABLE_HEADER_HEIGHT)
.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) .fontSize(16)
.maxFontSize(16)
.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) {
@@ -437,10 +457,12 @@ export struct AiPickNodeComponent {
this.cardData.tabs[this.tabIdx].fieldList, this.cardData.tabs[this.tabIdx].fieldList,
)) ))
.fontSize(16) .fontSize(16)
.maxFontSize(16)
.minFontSize(9)
.fontWeight(500)
.fontFamily('monospace') .fontFamily('monospace')
.fontColor(cellColor(this.cardData.tabs[this.tabIdx].keysList[1], item)) .fontColor(cellColor(this.cardData.tabs[this.tabIdx].keysList[1], item))
.layoutWeight(1) .layoutWeight(1)
.margin({ left: 8 })
.textAlign(TextAlign.End) .textAlign(TextAlign.End)
.maxLines(1) .maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis }) .textOverflow({ overflow: TextOverflow.Ellipsis })
@@ -451,27 +473,32 @@ export struct AiPickNodeComponent {
this.cardData.tabs[this.tabIdx].fieldList, this.cardData.tabs[this.tabIdx].fieldList,
)) ))
.fontSize(16) .fontSize(16)
.maxFontSize(16)
.minFontSize(9)
.fontWeight(500)
.fontFamily('monospace') .fontFamily('monospace')
.fontColor(cellColor(this.cardData.tabs[this.tabIdx].keysList[2], item)) .fontColor(cellColor(this.cardData.tabs[this.tabIdx].keysList[2], item))
.layoutWeight(1) .layoutWeight(1)
.margin({ left: 8 })
.textAlign(TextAlign.End) .textAlign(TextAlign.End)
.maxLines(1) .maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis }) .textOverflow({ overflow: TextOverflow.Ellipsis })
} }
.width('100%') .width('100%')
.height(44) .height(TABLE_ROW_HEIGHT)
.alignItems(VerticalAlign.Center) .alignItems(VerticalAlign.Center)
.border({ width: { bottom: 0.5 }, color: $r('app.color.divider_color') }) .border({ width: { top: 1 }, color: $r('app.color.divider_color') })
.onClick(() => { .onClick(() => {
this.jumpToDetail(item); this.jumpToDetail(item);
}) })
}, },
// 行情字段参与 key,确保 price/priceChg 变化后对应列表行重新渲染 // 行情字段参与 key,确保整体替换 CardData 后列表行同步刷新
(item: PickContractItem) => (item: PickContractItem) =>
`${item.contract}_${item.market}_${item.price ?? ''}_${item.priceChg ?? ''}`, `${item.contract}_${item.market}_${item.price ?? ''}_${item.priceChg ?? ''}`,
) )
} }
.width('100%')
.height(TABLE_HEIGHT)
}
// ── 一句话定制策略 ────────────────────────────────────── // ── 一句话定制策略 ──────────────────────────────────────
Row({ space: 2 }) { Row({ space: 2 }) {
@@ -485,29 +512,29 @@ export struct AiPickNodeComponent {
.margin({ top: 2 }) .margin({ top: 2 })
} }
.width('100%') .width('100%')
.height(CUSTOM_STRATEGY_HEIGHT)
.justifyContent(FlexAlign.Center) .justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center) .alignItems(VerticalAlign.Center)
.margin({ top: 6 })
.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');
} }
@@ -258,7 +258,7 @@ export struct MarketRankingNodeComponent {
.height(30) .height(30)
.linearGradient({ .linearGradient({
angle: 90, angle: 90,
colors: [[$r('app.color.transparent_white'), 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)
} }
@@ -16,6 +16,7 @@
{ "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": "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" }
] ]
} }