feat(ai-pick): 第二层 data_completed + 缓存两态
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { PickContractItem, PickTabData } from './AiPickTypes';
|
||||
import { CardData, PickContractItem, PickTabData } from './AiPickTypes';
|
||||
import { AiPickModel } from './AiPickModel';
|
||||
import { cellColor, cellValue, formatColName } from './AiPickUtils';
|
||||
import { MAX_ITEMS } from './AiPickConstant';
|
||||
@@ -10,7 +10,14 @@ interface StrategyDescParams {
|
||||
|
||||
@Component
|
||||
export struct AiPick {
|
||||
@State vm: AiPickModel = new AiPickModel();
|
||||
// 对应 Vue 端 cardData prop:从第一层 floorList 透传下来的单卡片配置
|
||||
@Prop cardData: CardData = {
|
||||
card_id: 0,
|
||||
card_key: 'placeholder',
|
||||
card_title: { value: '' },
|
||||
bury_point: '',
|
||||
};
|
||||
@State vm: AiPickModel = new AiPickModel(this.cardData);
|
||||
@State tabIdx: number = 0;
|
||||
|
||||
aboutToAppear(): void {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 对应 Vue 端 index.vue 里的 getData()(http() 封装 + 解析流程)
|
||||
// 目前网络请求未接入,直接返回 Mock 数据;接入时替换 fetchAiPickTabs 内部实现即可。
|
||||
import {
|
||||
CardData,
|
||||
PickContractItem,
|
||||
PickStrategy,
|
||||
PickTabData,
|
||||
@@ -22,7 +23,7 @@ import { MAX_ITEMS, MAX_TABS, MAX_TAGS } from './AiPickConstant';
|
||||
const MOCK_DELAY_MS = 300;
|
||||
|
||||
// 对应 Vue 端 index.vue 中 formatTabData 的单项处理(新版 isCustomVersion=true)
|
||||
function parseRawTab(item: RawPickTabItem): PickTabData {
|
||||
export function parseRawTab(item: RawPickTabItem): PickTabData {
|
||||
const isSystemPeriod = item.type === 'system_period';
|
||||
const strategyItem: RawStrategyItem = (
|
||||
isSystemPeriod ? item.system_strategy : item.custom_strategy
|
||||
@@ -73,13 +74,29 @@ function parseRawTab(item: RawPickTabItem): PickTabData {
|
||||
return tabData;
|
||||
}
|
||||
|
||||
// 获取 AI 选期视图数据(原始网络数据 + 解析为 PickTabData[])
|
||||
// TODO: 接入真实接口后,改为调用 @ohos.net.http 请求上述 URL,
|
||||
// 解析 JSON 响应体为 RawPickTabItem[] 后传入 parseRawTab
|
||||
export function fetchAiPickTabs(): Promise<PickTabData[]> {
|
||||
// 把 RawPickTabItem[] 解析成 PickTabData[](上限 MAX_TABS)
|
||||
export function parseRawTabs(rawTabs: RawPickTabItem[]): PickTabData[] {
|
||||
return rawTabs.slice(0, MAX_TABS).map(parseRawTab);
|
||||
}
|
||||
|
||||
// 获取 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
|
||||
export function fetchAiPickTabs(cardData: CardData): Promise<PickTabData[]> {
|
||||
return new Promise<PickTabData[]>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(MOCK_RAW_TABS.slice(0, MAX_TABS).map(parseRawTab));
|
||||
// 第一档:data_completed 内嵌数据
|
||||
if (cardData.data_completed) {
|
||||
const embedded = cardData.mod_data?.[0]?.data;
|
||||
if (embedded && embedded.length > 0) {
|
||||
resolve(parseRawTabs(embedded));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 第二档:HTTP 请求结果(当前用 mock 模拟)
|
||||
resolve(parseRawTabs(MOCK_RAW_TABS));
|
||||
}, MOCK_DELAY_MS);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,22 +1,57 @@
|
||||
import router from '@ohos.router';
|
||||
import { PickTabData } from './AiPickTypes';
|
||||
import { fetchAiPickTabs } from './AiPickApi';
|
||||
import { CardData, PickTabData } from './AiPickTypes';
|
||||
import { fetchAiPickTabs, parseRawTabs } from './AiPickApi';
|
||||
import { AiPickStorage } from './AiPickStorage';
|
||||
|
||||
@Observed
|
||||
export class AiPickModel {
|
||||
cardData: CardData;
|
||||
cardKey: string;
|
||||
|
||||
tabs: PickTabData[] = [];
|
||||
// 数据是否已就绪(对应 Vue 端 dataReady),View 层可据此展示 loading/骨架屏
|
||||
dataReady: boolean = false;
|
||||
// 请求是否失败(对应 Vue 端 dataError)
|
||||
dataError: boolean = false;
|
||||
|
||||
// 拉取并解析视图数据
|
||||
// 构造时即同步读出 showData,对应 Vue 端 useCardHandler.showData 三态:
|
||||
// 1. data_completed=true 且 mod_data[0].data 内嵌 → 直接解析内嵌数据(无需请求)
|
||||
// 2. 否则从卡片级缓存 AiPickStorage 读出(对应 __GLOBAL__.cardsDataCache)
|
||||
// 3. 都没有 → 空数组,View 层展示 loading;后续 loadData() 异步拉取
|
||||
constructor(cardData: CardData) {
|
||||
this.cardData = cardData;
|
||||
this.cardKey = cardData.card_key;
|
||||
this.tabs = this.computeShowData();
|
||||
this.dataReady = this.cardData.data_completed === true && this.tabs.length > 0;
|
||||
}
|
||||
|
||||
// 同步决策:内嵌 > 缓存 > 空
|
||||
private computeShowData(): PickTabData[] {
|
||||
if (this.cardData.data_completed === true) {
|
||||
const embedded = this.cardData.mod_data?.[0]?.data;
|
||||
if (embedded && embedded.length > 0) {
|
||||
return parseRawTabs(embedded);
|
||||
}
|
||||
}
|
||||
return AiPickStorage.get(this.cardKey) ?? [];
|
||||
}
|
||||
|
||||
// 拉取数据:
|
||||
// - data_completed=true:直接完成,不再发请求
|
||||
// - 其他:调用 fetchAiPickTabs,请求成功后写入 AiPickStorage
|
||||
async loadData(): Promise<void> {
|
||||
if (this.cardData.data_completed === true) {
|
||||
this.dataReady = true;
|
||||
this.dataError = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.tabs = await fetchAiPickTabs();
|
||||
const data = await fetchAiPickTabs(this.cardData);
|
||||
this.tabs = data;
|
||||
AiPickStorage.set(this.cardKey, data);
|
||||
this.dataError = false;
|
||||
} catch (e) {
|
||||
this.tabs = [];
|
||||
// 失败时保留原本的 showData(如有缓存命中)
|
||||
this.dataError = true;
|
||||
} finally {
|
||||
this.dataReady = true;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// AI选期第二层卡片级 session 缓存
|
||||
// 对应 Vue 端 __GLOBAL__.cardsDataCache[cardKey]
|
||||
// 鸿蒙侧不需要 Memory/AppStorage 桥接,直接用 Map;session 级,不持久化
|
||||
import { PickTabData } from './AiPickTypes';
|
||||
|
||||
export class AiPickStorage {
|
||||
private static cache: Map<string, PickTabData[]> = new Map();
|
||||
|
||||
static get(cardKey: string): PickTabData[] | undefined {
|
||||
return this.cache.get(cardKey);
|
||||
}
|
||||
|
||||
static set(cardKey: string, data: PickTabData[]): void {
|
||||
this.cache.set(cardKey, data);
|
||||
}
|
||||
|
||||
static has(cardKey: string): boolean {
|
||||
return this.cache.has(cardKey);
|
||||
}
|
||||
}
|
||||
@@ -107,3 +107,28 @@ export interface RawPickTabItem {
|
||||
system_strategy?: RawStrategyItem;
|
||||
custom_strategy?: RawStrategyItem;
|
||||
}
|
||||
|
||||
// ============ 第二层:卡片级配置 ============
|
||||
// 对应 Vue 端 floorList.card_list 中 ai-pick 那一项,从第一层透传到卡片
|
||||
export interface ModDataItem {
|
||||
url: string;
|
||||
param_list?: string[];
|
||||
req_desc?: string;
|
||||
// data_completed=true 时,直接把数据内嵌在这里(不再发请求)
|
||||
data?: RawPickTabItem[];
|
||||
}
|
||||
|
||||
export interface CardData {
|
||||
card_id: number;
|
||||
card_key: string;
|
||||
card_title: { value: string };
|
||||
// 对应 Vue 端 mod_data[0]
|
||||
mod_data?: ModDataItem[];
|
||||
// 对应 Vue 端 mod_data[0].data:当后端已经在第一层完成数据下发时为 true
|
||||
data_completed?: boolean;
|
||||
bury_point: string;
|
||||
}
|
||||
|
||||
// 对应 Vue 端 __GLOBAL__.cardsDataCache[cardKey],
|
||||
// second 层缓存,记录上一次请求成功的视图数据
|
||||
export type CardsDataCache = Record<string, PickTabData[]>;
|
||||
|
||||
@@ -2,6 +2,22 @@ import { Card } from '../common/Card';
|
||||
import { AIView } from '../ai-view/AIView';
|
||||
import { MarketRanking } from '../market-ranking/MarketRanking';
|
||||
import { AiPick } from '../ai-pick/AiPick';
|
||||
import { CardData } from '../ai-pick/AiPickTypes';
|
||||
|
||||
// 模拟 floorList 中 ai-pick 那一项 card_list 配置
|
||||
// 对应 src/src/pages/card-preview/mockFloorData.ts 第 950-975 行
|
||||
const aiPickCardData: CardData = {
|
||||
card_id: 5106,
|
||||
card_key: 'futuresHomepageAIPickCard',
|
||||
card_title: { value: 'AI选期' },
|
||||
bury_point: 'aiPick',
|
||||
data_completed: false,
|
||||
mod_data: [{
|
||||
url: 'https://ftapi.10jqka.com.cn/futgwapi/api/ai_diagnosis/home_strategy/v1/user_data',
|
||||
param_list: [''],
|
||||
req_desc: 'http_get',
|
||||
}],
|
||||
};
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
@@ -16,7 +32,7 @@ struct Index {
|
||||
MarketRanking()
|
||||
}
|
||||
Card() {
|
||||
AiPick()
|
||||
AiPick({ cardData: aiPickCardData })
|
||||
}
|
||||
}
|
||||
.width('100%')
|
||||
|
||||
Reference in New Issue
Block a user