af0025ce38
## Repository - HarmonyOS stage-mode HAR module, consumed by host app - No standalone build — hvigorw, oh_modules, and ../biz_quote absent here ## Architecture ### Feed Cards - FirstPage.createNodeView dispatches cards by FirstPageConstant.KEY_* - Card config from first_page_cards_config.json loaded by FirstPageCardsConfigLoader - Public entrypoint: Index.ets ### Market Ranking (src/main/ets/market-ranking/) - HTTP: MarketRankingDataFetcher fetches recommend_futures contracts - HQ: MarketRankingHqRequestClient subscribes via 4106/frame 2201/scenario qht_qihuo_sort - TCP reconnect rebuilds subscription from original HTTP contract list - Async callbacks guarded by request/subscription versions ### Other Cards - HotList, CommodityOptions, AiDiagnosis, AiRiseFall, SelfSelectedStock, CustomEntryList, FourEntryList - Each has DataFetcher + HQ request client + NodeComponent ## Key Changes (from CardHarmonyOS reference) - Added MetricId, PeriodId, SortOrder enums (MarketRankingModels.ets) - Replaced string comparisons with enum switches in thirdColumnTitle/Value/Color - Removed redundant undefined guards in thirdColumnColor/priceColor, delegated to riseFallColor - Inlined priceColor(), use riseFallColor directly - Tab bar Stack.height(30), Alignment.End (reverted from 46/TopEnd) - Name column fontWeight removed (default regular)
782 lines
23 KiB
Plaintext
782 lines
23 KiB
Plaintext
import { LengthMetrics, router } from '@kit.ArkUI';
|
||
import { emitter } from '@kit.BasicServicesKit';
|
||
import { BusinessError } from '@kit.BasicServicesKit';
|
||
import { AiDiagnosisDataFetcher } from '../datacenter/AiDiagnosisDataFetcher';
|
||
import { AiDiagnosisData, AllCardsCard, FactorItem, RecommendItem } from '../model/AiDiagnosisNodeModel';
|
||
import { FirstPageNodeModel } from '../../datacenter/model/FirstPageNodeModel';
|
||
import { AiDiagnosisRequestClient } from '../clients/AiDiagnosisRequestClient';
|
||
import { HeaderItem, RowData, CellArray, TableData } from '@b2b/hq_table';
|
||
import { EmitterConstants, GlobalContext, HXLog } from 'biz_common';
|
||
import { RouterUtil } from '../../util/RouterUtil';
|
||
import { enableDarkMode } from '@kernel/theme_manager';
|
||
import { TableConstants } from '@b2c/lib_baseui';
|
||
import { QuoteCustomSettingManager, StandardPriceType } from 'biz_quote';
|
||
import { TabType } from 'biz_quote/src/main/ets/config/tabConfig';
|
||
import util from '@ohos.util';
|
||
|
||
// 评分背景图片配置文件
|
||
const AI_SCORE_BG_IMAGES_FILE = 'ai_score_background_images.json'
|
||
|
||
// 评分背景图片配置数据结构
|
||
interface ScoreBgImagesConfig {
|
||
light: Record<string, string>
|
||
dark: Record<string, string>
|
||
}
|
||
|
||
/**
|
||
* AI 诊断节点组件
|
||
* @author yansong@myhexin.com
|
||
*/
|
||
|
||
// AI诊断卡片行情请求 FrameId 和 PageId
|
||
const FRAME_ID_AI_DIAGNOSIS = 2201
|
||
const PAGE_ID_AI_DIAGNOSIS = 4106
|
||
// 因子列表最大显示数量
|
||
const FACTOR_LIST_MAX = 3
|
||
|
||
@Component
|
||
export struct AiDiagnosisNodeComponent {
|
||
nodeModel: FirstPageNodeModel | null = null
|
||
|
||
// 刷新触发器(父组件通过 @Link 传入,每次递增触发刷新)
|
||
@Link @Watch('onRefreshTriggerChange') refreshTrigger: number
|
||
|
||
// Tab 可见性(用于控制行情请求的 request/release)
|
||
@Watch('onTabVisibilityChange') @Consume firstPageVisible: boolean = true
|
||
|
||
// 深色模式状态
|
||
@StorageProp(enableDarkMode) isDarkMode: boolean = false
|
||
|
||
@State private cardConfig: AllCardsCard | null = null
|
||
@State private cardData: AiDiagnosisData | null = null
|
||
|
||
// 行情数据
|
||
@State private priceChg: string = '--' // 涨跌幅
|
||
@State private priceChgColor: ResourceStr = $r('app.color.price_even') // 涨跌幅颜色
|
||
|
||
// 行情订阅客户端
|
||
private hqRequestClient: AiDiagnosisRequestClient | null = null
|
||
|
||
// 评分背景图片配置
|
||
private scoreBgImagesLight: Record<string, string> = {}
|
||
private scoreBgImagesDark: Record<string, string> = {}
|
||
|
||
// TCP 重连监听
|
||
private mIsEventSubscribed: boolean = false
|
||
private netRelinkCallBack = () => {
|
||
if (this.firstPageVisible) {
|
||
setTimeout(() => {
|
||
if (this.cardData && this.cardData.code && this.cardData.market) {
|
||
this.subscribeHqData(this.cardData.code, this.cardData.market)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// 上一次刷新时使用的基准价类型,用于在变可见时判断是否需要按新基准价重新请求
|
||
private lastStandardPriceType: StandardPriceType =
|
||
QuoteCustomSettingManager.getInstance().getStandardPriceType()
|
||
|
||
aboutToAppear(): void {
|
||
// 加载评分背景图片配置
|
||
this.loadScoreBgImages()
|
||
|
||
// 初始化数据获取器回调
|
||
AiDiagnosisDataFetcher.getInstance().onCardConfigChange = (config) => {
|
||
this.cardConfig = config
|
||
}
|
||
|
||
AiDiagnosisDataFetcher.getInstance().onCardDataChange = (data) => {
|
||
this.cardData = data
|
||
// 当卡片数据更新时,重新订阅行情
|
||
if (data && data.code && data.market) {
|
||
this.subscribeHqData(data.code, data.market)
|
||
}
|
||
}
|
||
|
||
// 初始请求数据
|
||
this.refreshCardData()
|
||
|
||
// 订阅 TCP 重连事件
|
||
if (this.firstPageVisible) {
|
||
this.subscribeTcpNetStatus()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载评分背景图片配置
|
||
*/
|
||
private loadScoreBgImages(): void {
|
||
const rawData = this.readRawFile(AI_SCORE_BG_IMAGES_FILE)
|
||
if (!rawData) {
|
||
HXLog.e('AiDiagnosisNodeComponent', 'loadScoreBgImages: read file failed')
|
||
return
|
||
}
|
||
|
||
try {
|
||
const config = JSON.parse(rawData) as ScoreBgImagesConfig
|
||
this.scoreBgImagesLight = config.light || {}
|
||
this.scoreBgImagesDark = config.dark || {}
|
||
HXLog.d('AiDiagnosisNodeComponent', 'loadScoreBgImages: success')
|
||
} catch (e) {
|
||
const error = e as BusinessError
|
||
HXLog.e('AiDiagnosisNodeComponent', 'loadScoreBgImages: parse error ' + error.message)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从 rawfile 读取配置文件
|
||
*/
|
||
private readRawFile(fileName: string): string {
|
||
try {
|
||
const context = GlobalContext.get()
|
||
const mgr = context.resourceManager
|
||
const rawFile = mgr.getRawFileContentSync(fileName)
|
||
let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true })
|
||
return textDecoder.decodeToString(rawFile, { stream: false })
|
||
} catch (e) {
|
||
const error = e as BusinessError
|
||
HXLog.e('AiDiagnosisNodeComponent', `readRawFile ${fileName} error: ${error.code} ${error.message}`)
|
||
return ''
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 刷新触发器变化时的回调
|
||
* 首页下拉刷新完成后会触发此方法
|
||
*/
|
||
onRefreshTriggerChange(): void {
|
||
HXLog.d('AiDiagnosisNodeComponent', 'onRefreshTriggerChange triggered, refreshTrigger: ' + this.refreshTrigger)
|
||
this.refreshCardData()
|
||
}
|
||
|
||
/**
|
||
* Tab 可见性变化时的回调
|
||
* 可见时请求行情数据,不可见时释放行情请求
|
||
*/
|
||
onTabVisibilityChange(): void {
|
||
HXLog.d('AiDiagnosisNodeComponent', 'onTabVisibilityChange: ' + this.firstPageVisible)
|
||
if (this.firstPageVisible) {
|
||
// 可见时重新请求数据
|
||
if (this.cardData && this.cardData.code && this.cardData.market) {
|
||
this.subscribeHqData(this.cardData.code, this.cardData.market)
|
||
}
|
||
// 订阅 TCP 重连事件
|
||
this.subscribeTcpNetStatus()
|
||
// 检测基准价是否切换,切换后重新订阅
|
||
const currentStandardPriceType = QuoteCustomSettingManager.getInstance().getStandardPriceType()
|
||
if (this.lastStandardPriceType !== currentStandardPriceType) {
|
||
HXLog.d('AiDiagnosisNodeComponent', 'Standard price type changed, resubscribe')
|
||
this.lastStandardPriceType = currentStandardPriceType
|
||
if (this.cardData && this.cardData.code && this.cardData.market) {
|
||
this.subscribeHqData(this.cardData.code, this.cardData.market)
|
||
}
|
||
}
|
||
} else {
|
||
// 不可见时释放行情订阅
|
||
this.releaseHqClient()
|
||
// 取消订阅 TCP 重连事件
|
||
this.unSubscribeTcpNetStatus()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 刷新卡片数据(诊断数据 + 重新订阅行情)
|
||
*/
|
||
refreshCardData(): void {
|
||
AiDiagnosisDataFetcher.getInstance().fetchCardConfig()
|
||
}
|
||
|
||
/**
|
||
* 释放行情订阅客户端
|
||
*/
|
||
private releaseHqClient(): void {
|
||
if (this.hqRequestClient) {
|
||
this.hqRequestClient.release(true)
|
||
this.hqRequestClient = null
|
||
}
|
||
}
|
||
|
||
aboutToDisappear(): void {
|
||
this.releaseHqClient()
|
||
this.unSubscribeTcpNetStatus()
|
||
}
|
||
|
||
/**
|
||
* 订阅 TCP 重连事件
|
||
*/
|
||
private subscribeTcpNetStatus(): void {
|
||
if (this.mIsEventSubscribed) {
|
||
return
|
||
}
|
||
this.mIsEventSubscribed = true
|
||
emitter.on({ eventId: EmitterConstants.NETWORK_TCP_RECONNECT }, this.netRelinkCallBack)
|
||
}
|
||
|
||
/**
|
||
* 取消订阅 TCP 重连事件
|
||
*/
|
||
private unSubscribeTcpNetStatus(): void {
|
||
if (!this.mIsEventSubscribed) {
|
||
return
|
||
}
|
||
this.mIsEventSubscribed = false
|
||
emitter.off(EmitterConstants.NETWORK_TCP_RECONNECT, this.netRelinkCallBack)
|
||
}
|
||
|
||
/**
|
||
* 订阅行情数据(涨跌幅)
|
||
* 参考自选股模块实现,使用 AiDiagnosisRequestClient
|
||
*/
|
||
private subscribeHqData(code: string, market: string): void {
|
||
// 释放之前的订阅
|
||
if (this.hqRequestClient) {
|
||
this.hqRequestClient.release(true)
|
||
this.hqRequestClient = null
|
||
}
|
||
|
||
// 创建行情请求头配置
|
||
const headerConfig: HeaderItem[] = [
|
||
new HeaderItem(TableConstants.DATA_ID_NAME, ''),
|
||
new HeaderItem(TableConstants.DATA_ID_PRICE, ''),
|
||
new HeaderItem(TableConstants.DATA_ID_ZF, '')
|
||
]
|
||
|
||
// 创建请求客户端(使用 AiDiagnosisRequestClient,参考自选股实现)
|
||
this.hqRequestClient = new AiDiagnosisRequestClient(
|
||
FRAME_ID_AI_DIAGNOSIS,
|
||
PAGE_ID_AI_DIAGNOSIS,
|
||
code,
|
||
market,
|
||
headerConfig,
|
||
null,
|
||
(tableData: TableData | string) => {
|
||
this.onHqDataReceive(tableData)
|
||
}
|
||
)
|
||
|
||
// 发送请求
|
||
this.hqRequestClient.request(0, 1)
|
||
}
|
||
|
||
/**
|
||
* 处理行情数据回调
|
||
*/
|
||
private onHqDataReceive(tableData: TableData | string): void {
|
||
if (tableData instanceof TableData) {
|
||
const rows = tableData?.tableRows
|
||
if (rows && rows.length > 0) {
|
||
for (let i = 0; i < rows.length; i++) {
|
||
const row = rows[i]
|
||
if (row instanceof RowData) {
|
||
const cols: CellArray = row.scrollableCols
|
||
if (cols && cols.length > 0) {
|
||
// 解析涨跌幅数据
|
||
for (let j = 0; j < cols.length; j++) {
|
||
const cellData = cols[j]
|
||
if (cellData.dataId === TableConstants.DATA_ID_ZF) {
|
||
this.priceChg = cellData.data || '--'
|
||
// 根据涨跌幅设置颜色
|
||
const numValue = parseFloat(this.priceChg)
|
||
if (!isNaN(numValue)) {
|
||
if (numValue > 0) {
|
||
this.priceChgColor = $r('app.color.price_up_100') // 红色
|
||
} else if (numValue < 0) {
|
||
this.priceChgColor = $r('app.color.price_down_100') // 绿色
|
||
} else {
|
||
this.priceChgColor = $r('app.color.price_even') // 灰色
|
||
}
|
||
} else {
|
||
this.priceChgColor = $r('app.color.price_even')
|
||
}
|
||
return
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 格式化涨跌幅,与AI看涨跌保持一致
|
||
*/
|
||
formatPriceChg(rawValue: string): string {
|
||
if (!rawValue || rawValue === '--') {
|
||
return '--'
|
||
}
|
||
const numValue = parseFloat(rawValue)
|
||
if (isNaN(numValue)) {
|
||
return '--'
|
||
}
|
||
if (numValue > 0) {
|
||
return `+${numValue.toFixed(2)}%`
|
||
} else if (numValue < 0) {
|
||
return `${numValue.toFixed(2)}%`
|
||
} else {
|
||
return '0.00%'
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取评分文案
|
||
*/
|
||
getScoreText(score: string | number): ResourceStr {
|
||
const numScore = parseInt(String(score))
|
||
if (isNaN(numScore)) {
|
||
return '--'
|
||
}
|
||
if (numScore === 0) {
|
||
return $r('app.string.score_neutral')
|
||
}
|
||
if (numScore > 0) {
|
||
if (numScore < 40) return $r('app.string.score_weak_bull')
|
||
if (numScore < 80) return $r('app.string.score_medium_bull')
|
||
return $r('app.string.score_strong_bull')
|
||
} else {
|
||
const absScore = Math.abs(numScore)
|
||
if (absScore < 40) return $r('app.string.score_weak_bear')
|
||
if (absScore < 80) return $r('app.string.score_medium_bear')
|
||
return $r('app.string.score_strong_bear')
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取评分颜色
|
||
*/
|
||
getScoreColor(score: string | number): ResourceStr {
|
||
const numScore = parseInt(String(score))
|
||
if (isNaN(numScore) || numScore === 0) {
|
||
return $r('app.color.ai_score_neutral')
|
||
}
|
||
return numScore > 0 ? $r('app.color.price_up_100') : $r('app.color.price_down_100') // 红涨绿跌
|
||
}
|
||
|
||
/**
|
||
* 获取因子方向颜色
|
||
*/
|
||
getFactorColor(judge: string): ResourceStr {
|
||
if (judge.includes('利多') || judge.includes('多')) {
|
||
return $r('app.color.price_up_100')
|
||
}
|
||
if (judge.includes('利空') || judge.includes('空')) {
|
||
return $r('app.color.price_down_100')
|
||
}
|
||
return $r('app.color.ai_score_neutral')
|
||
}
|
||
|
||
handleTitleClick() {
|
||
// 如果没有合约信息,则跳转到 WebView 详情页
|
||
if (!this.cardConfig?.card_url) {
|
||
return
|
||
}
|
||
const url = this.cardConfig.card_url.ios || this.cardConfig.card_url.android || ''
|
||
if (url.length === 0) {
|
||
return
|
||
}
|
||
// 使用 RouterUtil 处理 client.html 协议格式的 URL
|
||
RouterUtil.jumpPage(url, true)
|
||
}
|
||
|
||
/**
|
||
* 处理卡片点击
|
||
*/
|
||
handleCardClick(): void {
|
||
if (this.cardData && this.cardData.code && this.cardData.market) {
|
||
const codeList = this.cardData.code
|
||
const marketList = this.cardData.market
|
||
const nameList = this.cardData.name
|
||
this.jumpToQuote(codeList, marketList, codeList, marketList, nameList)
|
||
return
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 处理推荐合约点击
|
||
*/
|
||
handleRecommendClick(item: RecommendItem): void {
|
||
if (!item.code || !item.market) {
|
||
return
|
||
}
|
||
// 构建推荐合约列表(包括当前推荐合约)
|
||
const codeList = this.cardData?.recommend?.map(r => r.code).join(',') || item.code
|
||
const marketList = this.cardData?.recommend?.map(r => r.market).join(',') || item.market
|
||
const nameList = this.cardData?.recommend?.map(r => r.name).join(',') || item.name
|
||
|
||
// 跳转到行情详情页(分时页面)
|
||
this.jumpToQuote(item.code, item.market, codeList, marketList, nameList)
|
||
}
|
||
|
||
/**
|
||
* 跳转到行情详情页
|
||
*/
|
||
private jumpToQuote(code: string, market: string, codeList: string, marketList: string, nameList: string): void {
|
||
router.pushUrl({
|
||
url: 'pages/QuoteDetailPage',
|
||
params: {
|
||
marketId: market,
|
||
code: code,
|
||
name: nameList,
|
||
codeList: codeList,
|
||
marketIdList: marketList,
|
||
nameList: nameList,
|
||
periodIndex: 0, // 默认显示分时
|
||
defaultTabType: TabType.F10, // 切换到基本面(F10) Tab,
|
||
enableScrollToTargetTab: false // 禁止跳转到目标tab,保持在分时页顶部
|
||
}
|
||
}, router.RouterMode.Standard, (err) => {
|
||
if (err) {
|
||
HXLog.e('AiDiagnosisNodeComponent', `jumpToQuote failed: ${err.code}, ${err.message}`)
|
||
}
|
||
})
|
||
}
|
||
|
||
build() {
|
||
Column() {
|
||
// 标题栏
|
||
this.buildTitleBar()
|
||
|
||
// 卡片内容
|
||
if (this.cardData) {
|
||
this.buildCardContent()
|
||
}
|
||
}
|
||
.width('100%')
|
||
.backgroundColor($r('app.color.surface_layer1_foreground'))
|
||
.borderRadius(4)
|
||
.padding({ left: 10, right: 10, bottom: 10 })
|
||
}
|
||
|
||
@Builder
|
||
buildTitleBar() {
|
||
Row() {
|
||
Row() {
|
||
Text(this.cardConfig?.card_title?.value || $r('app.string.ai_diagnosis_title'))
|
||
.fontColor($r('app.color.elements_text_primary_02'))
|
||
.fontSize(18)
|
||
.fontWeight(500)
|
||
|
||
Image($r('app.media.icon_arrow_forward_20'))
|
||
.width(20)
|
||
.height(20)
|
||
.fillColor($r('app.color.elements_icon_primary_02'))
|
||
}
|
||
.alignItems(VerticalAlign.Center)
|
||
.height('100%')
|
||
.onClick(() => this.handleTitleClick())
|
||
|
||
Blank()
|
||
}
|
||
.width('100%')
|
||
.height(48)
|
||
}
|
||
|
||
@Builder
|
||
buildCardContent() {
|
||
Column() {
|
||
Column({ space: 12 }) {
|
||
// 合约信息
|
||
this.buildContractInfo()
|
||
|
||
// 评分区域
|
||
this.buildScoreArea()
|
||
}
|
||
.borderRadius(4)
|
||
.padding(8)
|
||
.linearGradient(this.getScoreAreaGradientConfig())
|
||
.onClick(() => this.handleCardClick())
|
||
|
||
// AI诊断说明
|
||
if (this.cardData && this.cardData.message && this.cardData.message.length > 0) {
|
||
this.buildDiagnosisText()
|
||
}
|
||
|
||
// 推荐合约
|
||
if (this.cardData && this.cardData.recommend && this.cardData.recommend.length > 0) {
|
||
this.buildRecommendContracts()
|
||
}
|
||
}
|
||
.width('100%')
|
||
.borderRadius(8)
|
||
}
|
||
|
||
/**
|
||
* 获取评分区域渐变配置
|
||
* 从上到下渐变:从不透明到透明
|
||
*/
|
||
getScoreAreaGradientConfig(): LinearGradientOptions {
|
||
const score = this.cardData ? parseInt(this.cardData.score) : 0
|
||
if (isNaN(score) || score === 0) {
|
||
// 橙色渐变
|
||
return {
|
||
direction: GradientDirection.Top,
|
||
colors: [[$r('app.color.ai_score_gradient_neutral_start'), 0.0], [$r('app.color.ai_score_gradient_neutral_end'), 1.0]]
|
||
}
|
||
}
|
||
if (score > 0) {
|
||
// 红色渐变
|
||
return {
|
||
direction: GradientDirection.Top,
|
||
colors: [[$r('app.color.ai_score_gradient_bull_start'), 0.0], [$r('app.color.ai_score_gradient_bull_end'), 1.0]]
|
||
}
|
||
}
|
||
// 绿色渐变
|
||
return {
|
||
direction: GradientDirection.Top,
|
||
colors: [[$r('app.color.ai_score_gradient_bear_start'), 0.0], [$r('app.color.ai_score_gradient_bear_end'), 1.0]]
|
||
}
|
||
}
|
||
|
||
@Builder
|
||
buildContractInfo() {
|
||
Row() {
|
||
Text(this.cardData ? this.cardData.name : '--')
|
||
.fontColor($r('app.color.elements_text_primary_02'))
|
||
.fontSize(16)
|
||
.textAlign(TextAlign.Center)
|
||
.height(20)
|
||
.fontWeight(500)
|
||
|
||
Blank().width(10)
|
||
|
||
// 涨跌幅
|
||
Text(this.formatPriceChg(this.priceChg))
|
||
.fontColor(this.priceChgColor)
|
||
.fontSize(14)
|
||
.fontWeight(500)
|
||
}
|
||
.width('100%')
|
||
}
|
||
|
||
@Builder
|
||
buildScoreArea() {
|
||
Row() {
|
||
// 评分圆盘
|
||
this.buildScoreDial()
|
||
|
||
// 因子表格
|
||
this.buildFactorTable()
|
||
}
|
||
.alignItems(VerticalAlign.Center)
|
||
.width('100%')
|
||
}
|
||
|
||
@Builder
|
||
buildScoreDial() {
|
||
Stack({ alignContent: Alignment.Bottom }) {
|
||
// 背景图片
|
||
Image(this.getScoreBackgroundImage(this.cardData ? this.cardData.score : '0'))
|
||
.borderRadius(12)
|
||
.objectFit(ImageFit.Contain)
|
||
|
||
// 文字叠加层
|
||
Column() {
|
||
Text(this.cardData ? this.cardData.score : '--')
|
||
.fontColor(this.getScoreTextColor())
|
||
.fontSize(20)
|
||
.fontWeight(FontWeight.Bold)
|
||
|
||
Text(this.getScoreText(this.cardData ? this.cardData.score : 0))
|
||
.fontColor($r('app.color.elements_text_tertiary'))
|
||
.fontSize(14)
|
||
}
|
||
.width('100%')
|
||
.margin({ bottom: 10 })
|
||
.justifyContent(FlexAlign.End)
|
||
.alignItems(HorizontalAlign.Center)
|
||
}
|
||
.layoutWeight(1)
|
||
.margin({ right: 12 })
|
||
}
|
||
|
||
/**
|
||
* 获取评分文字颜色
|
||
* 根据评分正负返回:红涨绿跌
|
||
*/
|
||
getScoreTextColor(): ResourceStr {
|
||
const score = this.cardData ? parseInt(this.cardData.score) : 0
|
||
if (isNaN(score) || score === 0) {
|
||
// 0或无效值:深色(黑色)
|
||
return $r('app.color.price_even')
|
||
}
|
||
// 正数红色,负数绿色
|
||
return score > 0 ? $r('app.color.price_up_100') : $r('app.color.price_down_100')
|
||
}
|
||
|
||
/**
|
||
* 根据评分和主题获取背景图片 URL
|
||
*/
|
||
getScoreBackgroundImage(score: string): string {
|
||
const images = this.isDarkMode ? this.scoreBgImagesDark : this.scoreBgImagesLight
|
||
const numScore = parseInt(score)
|
||
if (isNaN(numScore)) {
|
||
return images['neutral']
|
||
}
|
||
// 根据评分区间返回对应图片
|
||
if (numScore <= -60) {
|
||
return images['strong_bear']
|
||
} else if (numScore <= -40) {
|
||
return images['medium_bear']
|
||
} else if (numScore <= -20) {
|
||
return images['weak_bear']
|
||
} else if (numScore < 0) {
|
||
return images['slight_bear']
|
||
} else if (numScore === 0) {
|
||
return images['neutral']
|
||
} else if (numScore < 20) {
|
||
return images['slight_bull']
|
||
} else if (numScore < 40) {
|
||
return images['weak_bull']
|
||
} else if (numScore < 60) {
|
||
return images['medium_bull']
|
||
} else {
|
||
return images['strong_bull']
|
||
}
|
||
}
|
||
|
||
@Builder
|
||
buildFactorTable() {
|
||
Column() {
|
||
// 表头
|
||
Row() {
|
||
// 因子列 - 有右边框
|
||
Column() {
|
||
Text($r('app.string.table_header_factor'))
|
||
.fontColor($r('app.color.elements_text_secondary'))
|
||
.fontSize(12)
|
||
.width('100%')
|
||
}
|
||
.layoutWeight(1)
|
||
.padding({ left: 8, right: 8, top: 6, bottom: 6 })
|
||
.alignItems(HorizontalAlign.Start)
|
||
.border({
|
||
width: { right: 1 },
|
||
color: $r('app.color.elements_others_divider_primary')
|
||
})
|
||
|
||
// 方向列
|
||
Column() {
|
||
Text($r('app.string.table_header_direction'))
|
||
.fontColor($r('app.color.elements_text_secondary'))
|
||
.fontSize(12)
|
||
.width('100%')
|
||
.textAlign(TextAlign.End)
|
||
}
|
||
.width(50)
|
||
.padding({ left: 8, right: 8, top: 6, bottom: 6 })
|
||
.alignItems(HorizontalAlign.End)
|
||
}
|
||
.width('100%')
|
||
|
||
// 因子列表
|
||
ForEach((this.cardData ? this.cardData.factor_list.slice(0, FACTOR_LIST_MAX) : []), (item: FactorItem, index: number) => {
|
||
Row() {
|
||
// 因子列 - 有右边框
|
||
Column() {
|
||
Text(item.factor)
|
||
.fontColor($r('app.color.elements_text_primary_02'))
|
||
.fontSize(12)
|
||
.maxLines(1)
|
||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||
.width('100%')
|
||
}
|
||
.layoutWeight(1)
|
||
.padding({ left: 8, right: 8, top: 6, bottom: 6 })
|
||
.alignItems(HorizontalAlign.Start)
|
||
.border({
|
||
width: { right: 1 },
|
||
color: $r('app.color.elements_others_divider_primary')
|
||
})
|
||
|
||
// 方向列
|
||
Column() {
|
||
Text(item.judge)
|
||
.fontColor(this.getFactorColor(item.judge))
|
||
.fontSize(12)
|
||
.width('100%')
|
||
.textAlign(TextAlign.End)
|
||
.textVerticalAlign(TextVerticalAlign.CENTER)
|
||
}
|
||
.width(50)
|
||
.padding({ left: 8, right: 8, top: 6, bottom: 6 })
|
||
.alignItems(HorizontalAlign.End)
|
||
}
|
||
.border({
|
||
width: { top: 1 },
|
||
color: $r('app.color.elements_others_divider_primary')
|
||
})
|
||
.width('100%')
|
||
}, (item: FactorItem) => item.factor)
|
||
}
|
||
.layoutWeight(1)
|
||
.borderRadius(8)
|
||
.border({
|
||
width: 1,
|
||
color: $r('app.color.elements_others_divider_primary'),
|
||
radius: 4
|
||
})
|
||
}
|
||
|
||
@Builder
|
||
buildDiagnosisText() {
|
||
Column() {
|
||
Text() {
|
||
ImageSpan($r('app.media.ai_diagnosis_txt_icon'))
|
||
.width(41)
|
||
.height(18)
|
||
.objectFit(ImageFit.Contain)
|
||
.baselineOffset(LengthMetrics.vp(0))
|
||
.margin({right: 8})
|
||
|
||
Span(this.cardData ? this.cardData.message : '')
|
||
.fontColor($r('app.color.elements_text_secondary'))
|
||
.fontSize(14)
|
||
.lineHeight(20)
|
||
}
|
||
.maxLines(2)
|
||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||
.width('100%')
|
||
}
|
||
.padding(8)
|
||
.borderRadius(4)
|
||
.width('100%')
|
||
.backgroundColor($r('app.color.elements_others_bg_layer1'))
|
||
.onClick(() => this.handleCardClick())
|
||
|
||
}
|
||
|
||
@Builder
|
||
buildRecommendContracts() {
|
||
Row({ space: 8 }) {
|
||
Text($r('app.string.recommend_contracts'))
|
||
.fontColor($r('app.color.elements_text_secondary'))
|
||
.fontSize(12)
|
||
|
||
|
||
ForEach(this.cardData ? this.cardData.recommend : [], (item: RecommendItem, index: number) => {
|
||
Row({ space: 8 }) {
|
||
Text(item.name)
|
||
.fontColor($r('app.color.elements_text_primary_02'))
|
||
.fontSize(11)
|
||
.fontWeight(FontWeight.Medium)
|
||
.maxLines(1)
|
||
.textOverflow({ overflow: TextOverflow.Ellipsis })
|
||
|
||
Text(this.getScoreText(item.score))
|
||
.fontColor(this.getScoreColor(item.score))
|
||
.fontSize(11)
|
||
.fontWeight(FontWeight.Medium)
|
||
}
|
||
.padding({ left: 8, right: 8, top: 6, bottom: 6 })
|
||
.backgroundColor($r('app.color.elements_others_bg_layer1'))
|
||
.borderRadius(4)
|
||
.margin({ right: index < (this.cardData ? this.cardData.recommend.length : 0) - 1 ? 8 : 0 })
|
||
.onClick(() => this.handleRecommendClick(item))
|
||
}, (item: RecommendItem) => item.code + item.name)
|
||
}
|
||
.alignItems(VerticalAlign.Center)
|
||
.width('100%')
|
||
.margin({ top: 12 })
|
||
}
|
||
} |