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