fix(ai-pick): 修复 cardData 初始化与列表分隔线

This commit is contained in:
clz
2026-07-22 15:07:09 +08:00
parent bf6a38cab9
commit 0be60b394c
4 changed files with 43 additions and 42 deletions
+5 -21
View File
@@ -3,20 +3,14 @@ import { AiPickModel } from './AiPickModel';
import { cellColor, cellValue, formatColName } from './AiPickUtils';
import { MAX_ITEMS } from './AiPickConstant';
// @Builder 按引用传递要求单参数 + 对象字面量,用此 interface 包装 tabIdx
interface StrategyDescParams {
tabIdx: number;
}
@Component
export struct AiPick {
// 对应 Vue 端 cardData prop:从第一层 floorList 透传下来的单卡片配置
@Prop cardData: CardData = {
card_id: 0,
card_key: 'placeholder',
card_title: { value: '' },
bury_point: '',
};
// cardData prop:从第一层 floorList 透传下来的单卡片配置
@Watch('onCardDataChanged') @Prop cardData: CardData | undefined;
// 初值用 placeholder cardData 占位(仅类型占位,实际值由 aboutToAppear 用真实 cardData 创建)
@State vm: AiPickModel = new AiPickModel(this.cardData);
@State tabIdx: number = 0;
@@ -26,20 +20,16 @@ export struct AiPick {
this.vm.loadData();
}
// 对应 Vue 端 watch(() => props.cardData, () => cardHandler.value.setCardData(...))
// 父组件传入不同卡片配置时,重新构造 Model 并重新加载数据
@Watch('cardData')
onCardDataChanged(): void {
this.vm = new AiPickModel(this.cardData);
this.tabIdx = 0;
this.vm.loadData();
}
// 按引用传递:单参数对象字面量,$$.tabIdx 变化时内部 UI 自动刷新
@Builder
StrategyDesc($$: StrategyDescParams) {
// 图片:imgs[0] 白天,imgs[1] 夜间;对应 Vue 端 desc-text.vue 的 imgSrc computed
// 外层整体点击跳 jumpToDetail2tag 点击阻止冒泡后跳 jumpToLabel(对应 @click.stop
// 图片:imgs[0] 白天,imgs[1] 夜间
Row() {
// 左侧:问句 + 标签
Column({ space: 8 }) {
@@ -76,8 +66,7 @@ export struct AiPick {
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
// 右侧:策略图片(仅系统策略有图,对应 Vue 端 desc-text.vue 右侧 image
// 对应 Vue 端 v-if="isTrueArray(tabInfo.imgs) && imgSrc"
// 右侧:策略图片(仅系统策略有图,右侧 image)
// imgs 长度>0 且当前选中位(imgs[0],暂无主题切换)非空才渲染
if (this.vm.tabs[$$.tabIdx].strategy.imgs.length > 0 && this.vm.tabs[$$.tabIdx].strategy.imgs[0]) {
Image(this.vm.tabs[$$.tabIdx].strategy.imgs[0])
@@ -129,9 +118,6 @@ export struct AiPick {
MainContent() {
Column() {
// ── 标题栏 ──────────────────────────────────────────────
// 对应 Vue 端 card-title 组件:点击跳转 cardData.card_url(卡片配置的通用链接),
// 与"一句话定制策略"jumpToNewHome)无关;mockFloorData 中 ai-pick 的 card_url 为空,
// 故标题栏本身不绑定跳转行为,也不显示右箭头
Row() {
Text('AI选期')
.fontSize(18)
@@ -245,9 +231,7 @@ export struct AiPick {
}
.width('100%')
.padding({ top: 12, bottom: 12 })
.border(index === Math.min(MAX_ITEMS, this.vm.tabs[this.tabIdx].items.length) - 1
? undefined
: { width: { bottom: 0.5 }, color: $r('app.color.divider_color') })
.border({ width: { bottom: 0.5 }, color: $r('app.color.divider_color') })
.onClick(() => {
this.vm.jumpToDetail(item.contract, item.market);
})
+5 -10
View File
@@ -1,5 +1,4 @@
// AI选期接口层:负责从网络获取原始数据并解析为视图数据
// 对应 Vue 端 index.vue 里的 getData()http() 封装 + 解析流程)
// 目前网络请求未接入,直接返回 Mock 数据;接入时替换 fetchAiPickTabs 内部实现即可。
import {
CardData,
@@ -14,10 +13,10 @@ import { getResData, getTableList, ResData } from './AiPickUtils';
import { MOCK_RAW_TABS } from './AiPickMock';
import { MAX_ITEMS, MAX_TABS, MAX_TAGS } from './AiPickConstant';
// 新版接口地址(isCustomVersion=true):
// {API_HOST}futgwapi/api/ai_diagnosis/home_strategy/v1/user_data
// 旧版接口地址(isCustomVersion=false):
// {API_HOST}futgwapi/api/ai_diagnosis/system_strategy/v1/home_data
/**
* 新版接口地址(isCustomVersion=true):{API_HOST}futgwapi/api/ai_diagnosis/home_strategy/v1/user_data
* 旧版接口地址(isCustomVersion=false):{API_HOST}futgwapi/api/ai_diagnosis/system_strategy/v1/home_data
**/
// 模拟网络延迟(毫秒),验证 loading 状态和异步流程用;接入真实接口后删除
const MOCK_DELAY_MS = 300;
@@ -53,12 +52,9 @@ export function parseRawTab(item: RawPickTabItem): PickTabData {
const strategy: PickStrategy = {
id: strategyDetail.id,
name: strategyDetail.strategy_name,
// 对应 Vue 端 user_question || content:空字符串也会 fallback
detail: strategyDetail.user_question || strategyDetail.content || '',
// 对应 Vue 端 getDefaultValue(strategy_type, 'custom_strategy')
type: strategyDetail.strategy_type || 'custom_strategy',
periodType: item.type,
// 对应 Vue 端默认值 ['自编'](非空数组不会被替换,因为空数组本身是 truthy)
tagList: (strategyDetail.tag_list ?? ['自编']).slice(0, MAX_TAGS),
imgs,
count,
@@ -82,8 +78,7 @@ export function parseRawTabs(rawTabs: RawPickTabItem[]): PickTabData[] {
// 获取 AI 选期视图数据(对应 Vue 端 getData() + useCardHandler.showData 决策)
// data_completed=true:使用 mod_data[0].data 内嵌数据,不发请求
// data_completed=false:发起 HTTP 请求(当前走 Mock),并通过 AiPickStorage 写入卡片级缓存
// TODO: 接入真实接口后,改为调用 @ohos.net.http 请求 cardData.mod_data[0].url
// 解析 JSON 响应体为 RawPickTabItem[] 后传入 parseRawTabs
// TODO: 接入真实接口后,改为调用 @ohos.net.http 请求 cardData.mod_data[0].url解析 JSON 响应体为 RawPickTabItem[] 后传入 parseRawTabs
export function fetchAiPickTabs(cardData: CardData): Promise<PickTabData[]> {
return new Promise<PickTabData[]>((resolve) => {
setTimeout(() => {
+15 -8
View File
@@ -1,22 +1,29 @@
// AI选期卡片常量配置,供 AiPickModel / AiPickUtils / AiPick 共用
// 最多展示的策略 Tab 数(对应 Vue 端 getData 里 .slice(0, 5)
// 最多展示的策略 Tab 数
export const MAX_TABS = 5;
// 每个 Tab 最多展示的合约条数(对应 Vue 端 tabDataList 里 .slice(0, 3)
// 每个 Tab 最多展示的合约条数
export const MAX_ITEMS = 3;
// 策略标签最多展示条数(对应 Vue 端 tagList.slice(0, 3)
// 策略标签最多展示条数
export const MAX_TAGS = 3;
// 表格列数:合约名称 + 最多 2 个数据列(对应 Vue 端 getShowKeyList 的 slice(0, 3)
// 表格列数:合约名称 + 最多 2 个数据列
export const MAX_SHOW_KEYS = 3;
// 自定义 DOUBLE 字段最多取前 2 个用于表头(对应 Vue 端 showKey = doubleListKey.slice(0, 2)
// 自定义 DOUBLE 字段最多取前 2 个用于表头
export const MAX_DOUBLE_KEYS = 2;
// 字段黑名单:过滤 field_list 中不应展示的字段(对应 Vue 端 utils.ts 的 blackList
// 字段黑名单:过滤 field_list 中不应展示的字段
export const BLACK_LIST: string[] = [
'合约代码', '合约简称', '收盘价', '最新价', '涨跌幅', 'code',
'market_id', 'market_code', '最新涨跌幅',
'合约代码',
'合约简称',
'收盘价',
'最新价',
'涨跌幅',
'code',
'market_id',
'market_code',
'最新涨跌幅',
];
+18 -3
View File
@@ -5,6 +5,14 @@ import { fetchAiPickTabs, parseRawTabs } from './AiPickApi';
// 顶层缓存 key:对应 Vue 端 __GLOBAL__.cardsDataCache(跨组件/跨页面共享)
const APP_STORAGE_CACHE_KEY = 'cardsDataCache';
// 空 CardData 占位(仅用于锺型调用场景,实际使用需设 cardData 后重新构造 Model
const EMPTY_CARD_DATA: CardData = {
card_id: 0,
card_key: '',
card_title: { value: '' },
bury_point: '',
};
@Observed
export class AiPickModel {
cardData: CardData;
@@ -20,9 +28,11 @@ export class AiPickModel {
// 1. data_completed=true 且 mod_data[0].data 内嵌 → 直接解析内嵌数据(无需请求)
// 2. 否则从 AppStorage 卡片级缓存读出(对应 __GLOBAL__.cardsDataCache
// 3. 都没有 → 空数组,View 层展示 loading;后续 loadData() 异步拉取
constructor(cardData: CardData) {
this.cardData = cardData;
this.cardKey = cardData.card_key;
// cardData 可选:不传则使用空占位,Model 处于“未绑定”状态,
// 需在 View 层 aboutToAppear 后赋值真实的 cardData(参见 AiPick.ets 的 @Watch 逻辑)
constructor(cardData?: CardData) {
this.cardData = cardData ?? EMPTY_CARD_DATA;
this.cardKey = this.cardData.card_key;
this.tabs = this.computeShowData();
this.dataReady = this.cardData.data_completed === true && this.tabs.length > 0;
}
@@ -41,8 +51,13 @@ export class AiPickModel {
// 拉取数据:
// - data_completed=true:直接完成,不再发请求
// - 未绑定 cardData:不动作
// - 其他:调用 fetchAiPickTabs,请求成功后写入 AppStorage
async loadData(): Promise<void> {
if (this.cardKey === '') {
// 未绑定 cardData,不动作
return;
}
if (this.cardData.data_completed === true) {
this.dataReady = true;
this.dataError = false;