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 { AiPickModel } from './AiPickModel';
|
||||||
import { cellColor, cellValue, formatColName } from './AiPickUtils';
|
import { cellColor, cellValue, formatColName } from './AiPickUtils';
|
||||||
import { MAX_ITEMS } from './AiPickConstant';
|
import { MAX_ITEMS } from './AiPickConstant';
|
||||||
@@ -10,7 +10,14 @@ interface StrategyDescParams {
|
|||||||
|
|
||||||
@Component
|
@Component
|
||||||
export struct AiPick {
|
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;
|
@State tabIdx: number = 0;
|
||||||
|
|
||||||
aboutToAppear(): void {
|
aboutToAppear(): void {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// 对应 Vue 端 index.vue 里的 getData()(http() 封装 + 解析流程)
|
// 对应 Vue 端 index.vue 里的 getData()(http() 封装 + 解析流程)
|
||||||
// 目前网络请求未接入,直接返回 Mock 数据;接入时替换 fetchAiPickTabs 内部实现即可。
|
// 目前网络请求未接入,直接返回 Mock 数据;接入时替换 fetchAiPickTabs 内部实现即可。
|
||||||
import {
|
import {
|
||||||
|
CardData,
|
||||||
PickContractItem,
|
PickContractItem,
|
||||||
PickStrategy,
|
PickStrategy,
|
||||||
PickTabData,
|
PickTabData,
|
||||||
@@ -22,7 +23,7 @@ import { MAX_ITEMS, MAX_TABS, MAX_TAGS } from './AiPickConstant';
|
|||||||
const MOCK_DELAY_MS = 300;
|
const MOCK_DELAY_MS = 300;
|
||||||
|
|
||||||
// 对应 Vue 端 index.vue 中 formatTabData 的单项处理(新版 isCustomVersion=true)
|
// 对应 Vue 端 index.vue 中 formatTabData 的单项处理(新版 isCustomVersion=true)
|
||||||
function parseRawTab(item: RawPickTabItem): PickTabData {
|
export function parseRawTab(item: RawPickTabItem): PickTabData {
|
||||||
const isSystemPeriod = item.type === 'system_period';
|
const isSystemPeriod = item.type === 'system_period';
|
||||||
const strategyItem: RawStrategyItem = (
|
const strategyItem: RawStrategyItem = (
|
||||||
isSystemPeriod ? item.system_strategy : item.custom_strategy
|
isSystemPeriod ? item.system_strategy : item.custom_strategy
|
||||||
@@ -73,13 +74,29 @@ function parseRawTab(item: RawPickTabItem): PickTabData {
|
|||||||
return tabData;
|
return tabData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 AI 选期视图数据(原始网络数据 + 解析为 PickTabData[])
|
// 把 RawPickTabItem[] 解析成 PickTabData[](上限 MAX_TABS)
|
||||||
// TODO: 接入真实接口后,改为调用 @ohos.net.http 请求上述 URL,
|
export function parseRawTabs(rawTabs: RawPickTabItem[]): PickTabData[] {
|
||||||
// 解析 JSON 响应体为 RawPickTabItem[] 后传入 parseRawTab
|
return rawTabs.slice(0, MAX_TABS).map(parseRawTab);
|
||||||
export function fetchAiPickTabs(): Promise<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
|
||||||
|
export function fetchAiPickTabs(cardData: CardData): Promise<PickTabData[]> {
|
||||||
return new Promise<PickTabData[]>((resolve) => {
|
return new Promise<PickTabData[]>((resolve) => {
|
||||||
setTimeout(() => {
|
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);
|
}, MOCK_DELAY_MS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,57 @@
|
|||||||
import router from '@ohos.router';
|
import router from '@ohos.router';
|
||||||
import { PickTabData } from './AiPickTypes';
|
import { CardData, PickTabData } from './AiPickTypes';
|
||||||
import { fetchAiPickTabs } from './AiPickApi';
|
import { fetchAiPickTabs, parseRawTabs } from './AiPickApi';
|
||||||
|
import { AiPickStorage } from './AiPickStorage';
|
||||||
|
|
||||||
@Observed
|
@Observed
|
||||||
export class AiPickModel {
|
export class AiPickModel {
|
||||||
|
cardData: CardData;
|
||||||
|
cardKey: string;
|
||||||
|
|
||||||
tabs: PickTabData[] = [];
|
tabs: PickTabData[] = [];
|
||||||
// 数据是否已就绪(对应 Vue 端 dataReady),View 层可据此展示 loading/骨架屏
|
// 数据是否已就绪(对应 Vue 端 dataReady),View 层可据此展示 loading/骨架屏
|
||||||
dataReady: boolean = false;
|
dataReady: boolean = false;
|
||||||
// 请求是否失败(对应 Vue 端 dataError)
|
// 请求是否失败(对应 Vue 端 dataError)
|
||||||
dataError: boolean = false;
|
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> {
|
async loadData(): Promise<void> {
|
||||||
|
if (this.cardData.data_completed === true) {
|
||||||
|
this.dataReady = true;
|
||||||
|
this.dataError = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
this.tabs = await fetchAiPickTabs();
|
const data = await fetchAiPickTabs(this.cardData);
|
||||||
|
this.tabs = data;
|
||||||
|
AiPickStorage.set(this.cardKey, data);
|
||||||
this.dataError = false;
|
this.dataError = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.tabs = [];
|
// 失败时保留原本的 showData(如有缓存命中)
|
||||||
this.dataError = true;
|
this.dataError = true;
|
||||||
} finally {
|
} finally {
|
||||||
this.dataReady = true;
|
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;
|
system_strategy?: RawStrategyItem;
|
||||||
custom_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 { AIView } from '../ai-view/AIView';
|
||||||
import { MarketRanking } from '../market-ranking/MarketRanking';
|
import { MarketRanking } from '../market-ranking/MarketRanking';
|
||||||
import { AiPick } from '../ai-pick/AiPick';
|
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
|
@Entry
|
||||||
@Component
|
@Component
|
||||||
@@ -16,7 +32,7 @@ struct Index {
|
|||||||
MarketRanking()
|
MarketRanking()
|
||||||
}
|
}
|
||||||
Card() {
|
Card() {
|
||||||
AiPick()
|
AiPick({ cardData: aiPickCardData })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.width('100%')
|
.width('100%')
|
||||||
|
|||||||
Reference in New Issue
Block a user