diff --git a/entry/src/main/ets/ai-pick/AiPick.ets b/entry/src/main/ets/ai-pick/AiPick.ets index e52e49c..589a046 100644 --- a/entry/src/main/ets/ai-pick/AiPick.ets +++ b/entry/src/main/ets/ai-pick/AiPick.ets @@ -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 - // 外层整体点击跳 jumpToDetail2,tag 点击阻止冒泡后跳 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); }) diff --git a/entry/src/main/ets/ai-pick/AiPickApi.ets b/entry/src/main/ets/ai-pick/AiPickApi.ets index 27a918d..c6bfe14 100644 --- a/entry/src/main/ets/ai-pick/AiPickApi.ets +++ b/entry/src/main/ets/ai-pick/AiPickApi.ets @@ -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 { return new Promise((resolve) => { setTimeout(() => { diff --git a/entry/src/main/ets/ai-pick/AiPickConstant.ets b/entry/src/main/ets/ai-pick/AiPickConstant.ets index ecaf852..3093074 100644 --- a/entry/src/main/ets/ai-pick/AiPickConstant.ets +++ b/entry/src/main/ets/ai-pick/AiPickConstant.ets @@ -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', + '最新涨跌幅', ]; diff --git a/entry/src/main/ets/ai-pick/AiPickModel.ets b/entry/src/main/ets/ai-pick/AiPickModel.ets index 76825d3..c154219 100644 --- a/entry/src/main/ets/ai-pick/AiPickModel.ets +++ b/entry/src/main/ets/ai-pick/AiPickModel.ets @@ -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 { + if (this.cardKey === '') { + // 未绑定 cardData,不动作 + return; + } if (this.cardData.data_completed === true) { this.dataReady = true; this.dataError = false;