-
Notifications
You must be signed in to change notification settings - Fork 6
[add] Wealth Product selector page #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough新增完整的指数基金发现功能:包含前端 FundCard 与 SparkLine 组件、MobX 的 IndexFundModel 存储、Next.js API 路由(带 60s 内存缓存与合成数据降级)、以及集成的金融着陆页与相关样式与类型声明。 Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Page as FinanceLandingPage
participant Store as indexFundStore (MobX)
participant API as /api/finance/index-funds
participant Cache as In-Memory Cache (TTL=60s)
participant Source as AkShare / Synthetic
User->>Page: 打开页面或更改筛选
Page->>Store: loadPage(category, riskLevel)
Store->>API: GET /api/finance/index-funds?category=X&riskLevel=Y&limit=8
API->>Cache: 检查缓存
alt 缓存命中
Cache-->>API: 返回缓存快照
else 缓存未命中
API->>Source: 请求历史序列 (AkShare)
alt 拉取成功
Source-->>API: 返回历史数据
else 拉取失败
API->>Source: 生成确定性合成数据 (seeded)
Source-->>API: 返回合成序列
end
API->>Cache: 写入缓存(TTL=60s)
end
API-->>Store: 返回 IndexFundSnapshot[] 与 metadata
Store-->>Page: observable 更新触发重新渲染
Page->>Page: 渲染 FundCard 与 SparkLine SVG
Page-->>User: 展示基金列表 / 加载 / 空状态
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 重点审查建议:
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
🧹 Nitpick comments (1)
pages/api/finance/index-funds.ts (1)
100-100: 模块级缓存在 API Routes 中可接受,但需要注意 Serverless 限制Next.js API Routes 在 serverless 环境中每个请求可能使用不同的实例,模块级缓存可能不一致。如果部署到 Vercel 等 serverless 平台,建议迁移到 Redis 或 Next.js 内置的 unstable_cache。
考虑使用 Next.js 15 的 caching API:
import { unstable_cache } from 'next/cache'; const getCachedSnapshots = unstable_cache( async () => loadSnapshots(), ['index-fund-snapshots'], { revalidate: 60, tags: ['finance'] } );
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (7)
constants/finance.tsis excluded by none and included by nonefinance_PRD.mdis excluded by!**/*.mdand included by nonelib/akshare.tsis excluded by none and included by nonelib/finance.tsis excluded by none and included by nonepnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yamland included by nonestyles/Finance.module.scssis excluded by none and included by nonetypes/finance.tsis excluded by none and included by none
📒 Files selected for processing (4)
components/Finance/FundCard.tsx(1 hunks)models/Finance.ts(1 hunks)pages/api/finance/index-funds.ts(1 hunks)pages/finance/index.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
{pages,components}/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
{pages,components}/**/*.tsx: ALWAYS use React Bootstrap components instead of custom HTML elements in UI code
Use semantic HTML structure (article, header, section); usefor countable items,
for navigation; apply list-unstyled on first-level lists
All user-facing text MUST use the i18n t() function (no hardcoded strings)
Use React Bootstrap 2.10 components consistently for responsive design
Files:
pages/finance/index.tsxcomponents/Finance/FundCard.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use optional chaining and modern ECMAScript features
Let TypeScript infer types when possible to avoid verbose annotations
Import from established sources (e.g., ContentModel from mobx-github, utilities from web-utility) rather than reimplementing
Use minimal exports and avoid unnecessary custom implementations
Files:
pages/finance/index.tsxmodels/Finance.tscomponents/Finance/FundCard.tsxpages/api/finance/index-funds.ts
pages/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
For static generation, allow errors to bubble naturally (do not swallow errors)
Files:
pages/finance/index.tsx
models/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
models/**/*.ts: Import './Base' in model files to ensure proper GitHub client configuration
Use ContentModel with the configured client from mobx-github for content access
Use treeFrom utility from web-utility for hierarchical data structures
Files:
models/Finance.ts
{models,pages/api}/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Import and use the configured githubClient from models/Base.ts; do not create new GitHub API instances
Files:
models/Finance.tspages/api/finance/index-funds.ts
{models,pages/api}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Decode Base64 GitHub content using atob(item.content) when processing responses
Files:
models/Finance.tspages/api/finance/index-funds.ts
🧠 Learnings (3)
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to {pages,components}/**/*.tsx : All user-facing text MUST use the i18n t() function (no hardcoded strings)
Applied to files:
pages/finance/index.tsx
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to models/**/*.ts : Import './Base' in model files to ensure proper GitHub client configuration
Applied to files:
models/Finance.ts
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to models/**/*.ts : Use ContentModel with the configured client from mobx-github for content access
Applied to files:
models/Finance.ts
🧬 Code graph analysis (2)
pages/finance/index.tsx (4)
models/Translation.ts (2)
i18n(36-36)I18nContext(44-44)models/Finance.ts (2)
IndexFundFilter(6-9)indexFundStore(48-48)components/Layout/PageHead.tsx (1)
PageHead(11-23)components/Finance/FundCard.tsx (1)
FundCard(76-171)
models/Finance.ts (1)
models/Base.ts (1)
ownClient(19-22)
🔇 Additional comments (10)
components/Finance/FundCard.tsx (1)
21-43: Sparkline 组件性能优化得当useMemo 的使用避免了不必要的计算,polyline 和 gradient 的计算逻辑清晰。边界处理(空数组、除零保护)处理得当。
models/Finance.ts (3)
1-9: 模型结构符合规范正确导入
ownClientfrom'./Base',符合编码规范要求。类型定义清晰,IndexFundFilter 接口设计合理,支持可选的 category 和 riskLevel 过滤。根据 learnings 要求:在 model 文件中导入 './Base' 以确保正确的 GitHub 客户端配置。
16-46: ListModel 使用正确,loadPage 实现规范IndexFundModel 正确继承 ListModel,使用 ownClient 进行 API 调用。loadPage 方法实现规范:
- 正确构建 URLSearchParams 查询参数
- 处理可选过滤器(category、riskLevel)
- 返回符合 ListModel 要求的
{ pageData, totalCount }结构- 使用可选链和空值合并运算符处理边界情况
48-48: 单例导出符合最佳实践导出 indexFundStore 单例实例,便于跨组件共享状态,符合 MobX 使用模式。
pages/api/finance/index-funds.ts (4)
16-19: 常量定义清晰,缓存策略合理60 秒 TTL 平衡了数据新鲜度与 API 调用频率。HISTORY_SPARKLINE_POINTS 和 FALLBACK_HISTORY_POINTS 的区分使得展示数据与降级数据有不同的粒度。
213-233: 降级策略设计优秀错误处理健壮:在无法获取真实数据时,使用确定性的合成数据(seededRandom)而非随机数据,确保同一 symbol 每次降级返回相同的结果。这对用户体验和调试都很友好。同时记录 console.warn 便于监控。
273-300: API handler 实现规范
- 正确验证 HTTP 方法(仅允许 GET)
- 设置适当的 Cache-Control 头(s-maxage=60, stale-while-revalidate=300)
- 返回结构化的响应(data + meta)
- 统一的错误处理和状态码
160-168: seededRandom 实现的确定性保证数据一致性使用线性同余生成器(LCG)基于 symbol 字符串生成伪随机数,确保相同 symbol 每次调用生成相同的序列。这对降级场景非常重要。
pages/finance/index.tsx (2)
29-46: MobX observer 使用正确组件正确使用 observer 包装,能够响应 indexFundStore 的状态变化。本地状态管理(category、riskLevel)使用 useState 合理,filter 对象的构建方式符合 MobX 最佳实践。
192-215: ScrollList 集成正确正确传递 store、translator、filter 和 renderList 属性。renderList 回调中的响应式布局(Row/Col)使用得当,空状态处理完善。
- Replace hardcoded Chinese copy in /pages/finance and FundCard with i18n t() calls - Refactor finance constants to export i18n label keys instead of raw strings - Add finance-related translation entries for zh-CN, en-US, and zh-TW - Move Finance styles from global SCSS to co-located Finance.module.less - Tweak AkShare/finance data utilities and API handler to align with new flow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
models/Finance.ts (1)
24-34: loadPage 方法应遵循既有评审建议进行重构。当前实现存在以下问题:
- 方法签名与 ListModel 的分页机制集成不够清晰
- 查询构建逻辑应提取,保持方法职责单一
- 既往评审已提供更清晰的实现方案
请参考既往评审建议,应用以下重构:
+ @observable + accessor pageSize = 8; - async loadPage(page = this.pageIndex, limit = this.pageSize, filter: IndexFundFilter = {}) { - const query = buildURLData({ ...filter, limit }).toString(); - const path = query ? `${this.baseURI}?${query}` : this.baseURI; - - const { body } = await this.client.get<IndexFundAPIResponse>(path); - - const pageData = body?.data || []; - const totalCount = body?.meta?.total ?? pageData.length; - - return { pageData, totalCount }; - } + async loadPage(page: number, limit: number, filter: IndexFundFilter) { + const { body } = await this.client.get<IndexFundAPIResponse>( + `${this.baseURI}?${buildURLData({ ...filter, limit })}` + ); + + const pageData = body?.data || []; + const totalCount = body?.meta?.total ?? pageData.length; + + return { pageData, totalCount }; + }基于既往评审建议。
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
constants/finance.tsis excluded by none and included by nonelib/akshare.tsis excluded by none and included by nonelib/finance.tsis excluded by none and included by nonetranslation/en-US.tsis excluded by none and included by nonetranslation/zh-CN.tsis excluded by none and included by nonetranslation/zh-TW.tsis excluded by none and included by none
📒 Files selected for processing (5)
components/Finance/Finance.module.less(1 hunks)components/Finance/FundCard.tsx(1 hunks)models/Finance.ts(1 hunks)pages/api/finance/index-funds.ts(1 hunks)pages/finance/index.tsx(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- components/Finance/Finance.module.less
🚧 Files skipped from review as they are similar to previous changes (1)
- pages/finance/index.tsx
🧰 Additional context used
📓 Path-based instructions (5)
models/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
models/**/*.ts: Import './Base' in model files to ensure proper GitHub client configuration
Use ContentModel with the configured client from mobx-github for content access
Use treeFrom utility from web-utility for hierarchical data structures
Files:
models/Finance.ts
{models,pages/api}/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Import and use the configured githubClient from models/Base.ts; do not create new GitHub API instances
Files:
models/Finance.tspages/api/finance/index-funds.ts
{models,pages/api}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Decode Base64 GitHub content using atob(item.content) when processing responses
Files:
models/Finance.tspages/api/finance/index-funds.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use optional chaining and modern ECMAScript features
Let TypeScript infer types when possible to avoid verbose annotations
Import from established sources (e.g., ContentModel from mobx-github, utilities from web-utility) rather than reimplementing
Use minimal exports and avoid unnecessary custom implementations
Files:
models/Finance.tspages/api/finance/index-funds.tscomponents/Finance/FundCard.tsx
{pages,components}/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
{pages,components}/**/*.tsx: ALWAYS use React Bootstrap components instead of custom HTML elements in UI code
Use semantic HTML structure (article, header, section); usefor countable items,
for navigation; apply list-unstyled on first-level lists
All user-facing text MUST use the i18n t() function (no hardcoded strings)
Use React Bootstrap 2.10 components consistently for responsive design
Files:
components/Finance/FundCard.tsx
🧠 Learnings (7)
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to models/**/*.ts : Use ContentModel with the configured client from mobx-github for content access
Applied to files:
models/Finance.ts
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to {pages,components}/**/*.tsx : All user-facing text MUST use the i18n t() function (no hardcoded strings)
Applied to files:
components/Finance/FundCard.tsx
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to {pages,components}/**/*.tsx : Use React Bootstrap 2.10 components consistently for responsive design
Applied to files:
components/Finance/FundCard.tsx
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to {pages,components}/**/*.tsx : ALWAYS use React Bootstrap components instead of custom HTML elements in UI code
Applied to files:
components/Finance/FundCard.tsx
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to translation/{zh-CN,en-US,zh-TW}.ts : Prefer generic translation keys unless specifically scoped (e.g., t('knowledge_base') over t('policy_documents'))
Applied to files:
components/Finance/FundCard.tsx
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to translation/{zh-CN,en-US,zh-TW}.ts : Remove unused translation keys when replacing with generic ones
Applied to files:
components/Finance/FundCard.tsx
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to {pages,components}/**/*.tsx : Use semantic HTML structure (article, header, section); use <ol> for countable items, <ul> for navigation; apply list-unstyled on first-level lists
Applied to files:
components/Finance/FundCard.tsx
🧬 Code graph analysis (3)
models/Finance.ts (1)
models/Base.ts (1)
ownClient(19-22)
pages/api/finance/index-funds.ts (1)
pages/api/core.ts (1)
safeAPI(16-45)
components/Finance/FundCard.tsx (1)
models/Translation.ts (1)
I18nContext(44-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build-and-Deploy
🔇 Additional comments (10)
models/Finance.ts (1)
18-22: 类定义结构正确,符合 MobX 规范。IndexFundModel 正确继承 ListModel,使用 ownClient 符合编码规范(避免重复创建 GitHub API 实例),@observable accessor 用法正确。
components/Finance/FundCard.tsx (4)
22-74: Sparkline 组件实现优秀。
- 正确使用 useMemo 优化 polyline 计算,避免不必要的重渲染
- aria-label 已正确国际化
- 梯度渐变和 SVG 路径生成逻辑清晰
76-132: 组件头部和描述区域实现正确。
- 正确解构 IndexFundSnapshot props,TypeScript 类型安全
- React Bootstrap Card 和 Badge 组件使用规范
- 所有用户可见文本已通过 t() 国际化
134-146: 指标行使用语义化 HTML 标记,符合规范。
- 使用 dl/dt/dd 描述列表结构,语义清晰
- 所有指标标签已正确国际化(t() 函数)
- formatNumber 和 formatPercent 辅助函数应用得当
- valueTone 动态样式类根据正负值着色,用户体验友好
注意:formatNumber 的 locale 硬编码问题已在单独评论中标记。
162-164: 时间元素使用语义化标记,符合 HTML5 规范。使用
<time>元素配合 dateTime 属性,提升可访问性和 SEO,更新时间文本已正确国际化。pages/api/finance/index-funds.ts (5)
19-22: 常量定义清晰合理。缓存 TTL 为 60 秒,适合金融数据的实时性要求;历史数据点数量配置明确。
116-136: AkShare 数据处理健壮且规范。
- unwrapAkShareRecords 正确处理多种响应格式(直接数组、data 字段、result 字段)
- normalizeHistory 支持双语字段名(date/日期、close/收盘),适配 AkShare API 特性
- TypeScript 类型守卫确保类型安全,日期排序逻辑正确
157-189: 合成数据回退机制设计优秀。
- seededRandom 基于 symbol 生成确定性随机序列,确保同一基金的回退数据可复现
- 合成走势加入 ±2% 噪声,模拟真实波动
- 提供了弹性和可用性保障,即使外部数据源不可用,用户仍能看到演示数据
这是防御性编程的良好实践。
211-242: 快照构建和缓存逻辑正确。
- buildSnapshot 使用 try/catch 捕获异常,失败时降级到合成数据
- 日志记录(console.warn)便于监控和调试
- 缓存机制基于时间戳和 TTL 判断,避免频繁请求外部 API
- Promise.all 并行加载所有基金数据,性能优化得当
275-301: API 路由实现专业且完整。
- safeAPI 中间件统一处理错误,避免服务器崩溃
- 响应包含 meta 元数据(total、returned、cached、source),便于前端调试和监控
- Cache-Control 标头设置 's-maxage=60, stale-while-revalidate=300',利用 CDN 缓存和过期重新验证策略,优化性能
- 过滤逻辑处理查询参数数组格式,健壮性强
基于 learnings:使用 safeAPI 包装器是既定模式。
| <ul className="list-unstyled m-0 d-flex flex-wrap gap-2"> | ||
| {tags?.map(tag => ( | ||
| <Badge key={tag} as="li" bg="light" text="dark" className={styles.tagBadge}> | ||
| {tag} | ||
| </Badge> | ||
| ))} | ||
| </ul> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
标签(tags)未国际化处理。
当前 tags 数组中的字符串(如 '宽基'、'蓝筹')直接渲染,在非中文环境下会显示中文文本,违反国际化规范。
建议方案:
方案 1(推荐):在 constants/finance.ts 中定义标签映射,类似 INDEX_CATEGORY_LABEL_KEYS:
// constants/finance.ts
export const INDEX_TAG_LABEL_KEYS: Record<string, string> = {
'宽基': 'fund_tag_broad_base',
'蓝筹': 'fund_tag_blue_chip',
'成长': 'fund_tag_growth',
// ... 其他标签
};然后在组件中映射:
<ul className="list-unstyled m-0 d-flex flex-wrap gap-2">
{tags?.map(tag => (
<Badge key={tag} as="li" bg="light" text="dark" className={styles.tagBadge}>
- {tag}
+ {t(INDEX_TAG_LABEL_KEYS[tag] || tag)}
</Badge>
))}
</ul>方案 2:API 返回标签的 key 而非中文字符串,组件通过 key 查找翻译。
基于 learnings:所有用户可见文本必须使用 t() 函数。
🤖 Prompt for AI Agents
In components/Finance/FundCard.tsx around lines 154 to 160, the tags array is
rendered as raw Chinese strings which bypasses i18n; replace direct rendering
with translated labels by mapping tag values to translation keys (add an
INDEX_TAG_LABEL_KEYS or INDEX_TAG_KEY_MAP in constants/finance.ts mapping
Chinese tag strings to i18n keys), then in the component map each tag to
t(INDEX_TAG_LABEL_KEYS[tag] || tag) when rendering the Badge (use the original
tag as a safe fallback if no mapping exists); alternatively, if the API can be
changed, accept tag keys and call t(key) directly—ensure every user-visible tag
uses t() before render.
[optimize] simplify CSS modules & Finance page [migrate] upgrade to Next.js 16, Koa Router 15, Marked 17, ECharts-JSX 0.6 & other latest Upstream packages
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (4)
components/Finance/FundCard.tsx (3)
10-17: 数值/百分比格式化可以进一步收紧类型并统一 locale 处理目前
formatNumber已支持传入locale并在调用处使用currentLanguage,比之前硬编码'zh-CN'更合理。不过可以进一步改进两点:
- 将第二个参数类型收紧为
LanguageCode,例如从models/Translation中引入后写成:
const formatNumber = (value?: number | null, locale: LanguageCode = 'zh-CN') => ...;
这样可以防止误传无效 locale 字符串。formatPercent仍然是固定toFixed(2)拼接"%",如果希望百分比的小数点和分组符也随 locale 变化,可以比照formatNumber接收locale,内部通过toLocaleString处理,再拼接%。这两点都属于增强 i18n 体验的优化,可按业务需要择机调整。
67-70: 数据源文本的换行建议用块级结构代替<br />现在通过
<small>内嵌一个<br />强制换行,语义上略显生硬,也不利于样式复用。建议改为两个块级(或d-block)元素,例如:- <small className="text-muted text-end"> - {t('data_source')} <br /> - {source.historyEndpoint} - </small> + <small className="text-muted text-end d-flex flex-column"> + <span>{t('data_source')}</span> + <span>{source.historyEndpoint}</span> + </small>既保持当前视觉,又更符合结构化 HTML 的最佳实践。
91-97: tags 仍然是硬编码中文字符串,需通过 i18n 映射
tags数组目前直接渲染{tag},如果后端传入的是中文标签(如“宽基”、“蓝筹”),在非中文语言下就会违背“所有面向用户的文本必须通过t()”的规范,也与之前的 review 建议不符。建议:
在
constants/finance.ts中新增映射,例如:export const INDEX_TAG_LABEL_KEYS: Record<string, I18nKey> = { 宽基: 'fund_tag_broad_base', 蓝筹: 'fund_tag_blue_chip', // ... };组件中渲染时做 key 映射并通过
t()输出:- {tags?.map(tag => ( - <Badge key={tag} as="li" bg="light" text="dark" className={styles.tagBadge}> - {tag} - </Badge> - ))} + {tags?.map(tag => ( + <Badge key={tag} as="li" bg="light" text="dark" className={styles.tagBadge}> + {t(INDEX_TAG_LABEL_KEYS[tag] || tag)} + </Badge> + ))}或让 API 直接返回标签 key,再在前端
t(key)渲染。pages/finance/index.tsx (1)
60-72: filterSummary 中的分隔符建议也通过 i18n 控制
filterSummary里目前是:return `${categoryText} · ${riskText}`;虽然只是一个“·”分隔符,但它依然属于用户可见文本,不同语言对分隔符和前后空格的习惯可能不同。建议将整个 summary 或至少分隔符抽成翻译 key,例如:
return t('finance_filter_summary', { category: categoryText, risk: riskText }); // 或者 return `${categoryText}${t('finance_filter_separator')}${riskText}`;这样在不同语言里可以自由调整格式。
🧹 Nitpick comments (3)
components/Finance/SparkLine.tsx (1)
14-88: SparkLine 计算与 i18n 使用整体正确,仅有微小优化空间
chartData对空数据和等值数据的处理都很稳健,SVG aria-label 和无数据占位都通过t()国际化,符合现有规范。唯一可以考虑的小优化是:gradientOffsets是完全常量,可以提到文件顶层常量(如const GRADIENT_STOPS = [0, 50, 100] as const;),chartData直接返回该常量,避免每次@computed访问时新建数组——当前性能影响很小,可视情况再做。components/Finance/FundCard.tsx (1)
99-105: 内部路由跳转建议改用 React Bootstrap / Next Link,避免完整刷新目前底部“查看详情”使用原生
<a href={/finance/${symbol}}>,在 Next.js 环境下会触发整页刷新,不利于性能和体验,也与“统一使用 React Bootstrap 组件”的约定不太一致。可以考虑:-import { Badge, Card } from 'react-bootstrap'; +import { Badge, Card, Button } from 'react-bootstrap'; ... - <a href={`/finance/${symbol}`} className="text-primary fw-semibold"> - {t('view_details')} - </a> + <Button + variant="link" + href={`/finance/${symbol}`} + className="p-0 text-primary fw-semibold align-self-start" + > + {t('view_details')} + </Button>或使用
next/link包裹,保持客户端路由行为。pages/finance/index.tsx (1)
76-117: 分段筛选按钮可考虑改用 React Bootstrap Button 以统一组件风格
renderCategory/renderRisk目前使用原生<button>配合styles.button/styles.active控制样式,在功能上完全可用。但从整体代码风格和规范里“优先使用 React Bootstrap 组件”的角度,可以考虑改为:-import { Badge, Button, Col, Container, Row } from 'react-bootstrap'; +import { Badge, Button, Col, Container, Row } from 'react-bootstrap'; ... - return ( - <button - key={labelKey} - type="button" - className={`${styles.button} ${active ? styles.active : ''}`} - onClick={...} - > - {t(labelKey)} - </button> - ); + return ( + <Button + key={labelKey} + type="button" + className={`${styles.button} ${active ? styles.active : ''}`} + onClick={...} + > + {t(labelKey)} + </Button> + );同理可应用于
renderRisk。保留现有样式 class 的同时,交互语义上完全由 React Bootstrap 接管,更利于后续统一维护。
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
README.mdis excluded by!**/*.mdand included by noneconstants/finance.tsis excluded by none and included by noneeslint.config.tsis excluded by none and included by nonelib/akshare.tsis excluded by none and included by nonepackage.jsonis excluded by none and included by nonepnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yamland included by none
📒 Files selected for processing (9)
components/Finance/Finance.module.less(1 hunks)components/Finance/FundCard.tsx(1 hunks)components/Finance/SparkLine.module.less(1 hunks)components/Finance/SparkLine.tsx(1 hunks)models/Finance.ts(1 hunks)models/Translation.ts(1 hunks)pages/api/finance/index-funds.ts(1 hunks)pages/finance/index.module.less(1 hunks)pages/finance/index.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- pages/api/finance/index-funds.ts
🧰 Additional context used
📓 Path-based instructions (6)
models/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
models/**/*.ts: Import './Base' in model files to ensure proper GitHub client configuration
Use ContentModel with the configured client from mobx-github for content access
Use treeFrom utility from web-utility for hierarchical data structures
Files:
models/Finance.tsmodels/Translation.ts
{models,pages/api}/**/*.ts
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Import and use the configured githubClient from models/Base.ts; do not create new GitHub API instances
Files:
models/Finance.tsmodels/Translation.ts
{models,pages/api}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Decode Base64 GitHub content using atob(item.content) when processing responses
Files:
models/Finance.tsmodels/Translation.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use optional chaining and modern ECMAScript features
Let TypeScript infer types when possible to avoid verbose annotations
Import from established sources (e.g., ContentModel from mobx-github, utilities from web-utility) rather than reimplementing
Use minimal exports and avoid unnecessary custom implementations
Files:
models/Finance.tspages/finance/index.tsxmodels/Translation.tscomponents/Finance/SparkLine.tsxcomponents/Finance/FundCard.tsx
{pages,components}/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
{pages,components}/**/*.tsx: ALWAYS use React Bootstrap components instead of custom HTML elements in UI code
Use semantic HTML structure (article, header, section); usefor countable items,
for navigation; apply list-unstyled on first-level lists
All user-facing text MUST use the i18n t() function (no hardcoded strings)
Use React Bootstrap 2.10 components consistently for responsive design
Files:
pages/finance/index.tsxcomponents/Finance/SparkLine.tsxcomponents/Finance/FundCard.tsx
pages/**/*.tsx
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
For static generation, allow errors to bubble naturally (do not swallow errors)
Files:
pages/finance/index.tsx
🧠 Learnings (9)
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to models/**/*.ts : Import './Base' in model files to ensure proper GitHub client configuration
Applied to files:
models/Finance.ts
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to models/**/*.ts : Use ContentModel with the configured client from mobx-github for content access
Applied to files:
models/Finance.ts
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to {pages,components}/**/*.tsx : All user-facing text MUST use the i18n t() function (no hardcoded strings)
Applied to files:
pages/finance/index.tsxcomponents/Finance/FundCard.tsx
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to translation/{zh-CN,en-US,zh-TW}.ts : Remove unused translation keys when replacing with generic ones
Applied to files:
pages/finance/index.tsxmodels/Translation.tscomponents/Finance/FundCard.tsx
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to translation/{zh-CN,en-US,zh-TW}.ts : Prefer generic translation keys unless specifically scoped (e.g., t('knowledge_base') over t('policy_documents'))
Applied to files:
pages/finance/index.tsxmodels/Translation.tscomponents/Finance/FundCard.tsx
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to translation/{zh-CN,en-US,zh-TW}.ts : Add translation keys to ALL language files (zh-CN.ts, en-US.ts, zh-TW.ts) when introducing text
Applied to files:
pages/finance/index.tsxmodels/Translation.ts
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to {pages,components}/**/*.tsx : Use semantic HTML structure (article, header, section); use <ol> for countable items, <ul> for navigation; apply list-unstyled on first-level lists
Applied to files:
pages/finance/index.tsxcomponents/Finance/FundCard.tsx
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to {pages,components}/**/*.tsx : Use React Bootstrap 2.10 components consistently for responsive design
Applied to files:
components/Finance/FundCard.tsx
📚 Learning: 2025-09-11T00:30:03.492Z
Learnt from: CR
Repo: Open-Source-Bazaar/Open-Source-Bazaar.github.io PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-11T00:30:03.492Z
Learning: Applies to {pages,components}/**/*.tsx : ALWAYS use React Bootstrap components instead of custom HTML elements in UI code
Applied to files:
components/Finance/FundCard.tsx
🧬 Code graph analysis (4)
models/Finance.ts (1)
models/Base.ts (1)
ownClient(19-22)
pages/finance/index.tsx (3)
models/Translation.ts (3)
I18nKey(21-21)i18n(38-38)I18nContext(46-46)models/Finance.ts (2)
IndexFundFilter(8-11)indexFundStore(35-35)components/Layout/PageHead.tsx (1)
PageHead(11-23)
components/Finance/SparkLine.tsx (2)
pages/finance/index.tsx (1)
observer(53-275)models/Translation.ts (2)
i18n(38-38)I18nContext(46-46)
components/Finance/FundCard.tsx (1)
models/Translation.ts (1)
I18nContext(46-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Build-and-Deploy
🔇 Additional comments (5)
models/Translation.ts (1)
21-21: I18nKey 类型设计合理,但注意保持多语言 key 一致用
keyof typeof zhCN约束 i18n key 很好,可以在编译期防止拼写错误。建议同步确认:本次新增的finance_*/index_*等文案 key 已在zh-CN.ts、en-US.ts、zh-TW.ts三个翻译文件中全部补齐,避免其它语言运行时才暴露缺失。Based on learnings, translation/{zh-CN,en-US,zh-TW}.ts 需要保持 key 集合一致。
components/Finance/SparkLine.module.less (1)
1-17: SparkLine 样式与组件结构匹配良好容器高度、SVG 尺寸和占位样式都与组件实现一一对应,当前实现简单直接,没有明显可见问题。
components/Finance/Finance.module.less (1)
1-41: FundCard 相关样式命名清晰,状态色选择合理卡片、指标和值、标签和正负色样式与 FundCard 组件的语义结构相匹配,色值也与 Bootstrap 成功/危险色保持一致,当前实现可直接接受。
pages/finance/index.module.less (1)
1-100: Finance 页面样式组织良好,与 TSX 结构一一对应各区域(hero、筛选面板、列表卡片、空状态、教育区)的阴影、圆角和布局 class 与
pages/finance/index.tsx中的使用保持一致,分段选择器.segmentedControl .button/.active也能很好支持自定义筛选按钮,目前没有发现样式层面的明显问题。pages/finance/index.tsx (1)
123-271: FinanceLandingPage 结构清晰,MobX 与 i18n 集成规范主渲染流程将页面拆成 hero、筛选面板、基金列表和教育区域四大块:
- 使用
Container/Row/Col/Badge/Button等 React Bootstrap 组件构建布局,符合项目既有风格;- 所有标题、文案、按钮和空状态都通过
t()调用 i18n key,未发现新的硬编码文本;- 筛选状态通过
@observable accessor filter管理,并在ScrollList的filter属性中直接透传,结合indexFundStore的分页模型,整体数据流比较清晰。在 models 中修正分页参数后,这个页面可以比较平滑地支持更多数据和滚动加载。
| async loadPage(page = this.pageIndex, limit = this.pageSize, filter: IndexFundFilter = {}) { | ||
| const { body } = await this.client.get<IndexFundAPIResponse>( | ||
| `${this.baseURI}?${buildURLData({ ...filter, limit })}`, | ||
| ); | ||
| const pageData = body?.data || []; | ||
| const totalCount = body?.meta?.total ?? pageData.length; | ||
|
|
||
| return { pageData, totalCount }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "Finance.ts" -type fRepository: Open-Source-Bazaar/Open-Source-Bazaar.github.io
Length of output: 110
🏁 Script executed:
find . -name "index-funds.ts" -type f | head -20Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io
Length of output: 125
🏁 Script executed:
find . -name "ListModel.ts" -o -name "Base.ts" | grep -E "models/(ListModel|Base)"Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io
Length of output: 107
🏁 Script executed:
cat -n ./models/Finance.tsRepository: Open-Source-Bazaar/Open-Source-Bazaar.github.io
Length of output: 1385
🏁 Script executed:
cat -n ./pages/api/finance/index-funds.tsRepository: Open-Source-Bazaar/Open-Source-Bazaar.github.io
Length of output: 10131
🏁 Script executed:
cat -n ./models/Base.tsRepository: Open-Source-Bazaar/Open-Source-Bazaar.github.io
Length of output: 2665
🏁 Script executed:
rg "ListModel|loadPage" --type ts -A 3 -B 1 | head -100Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io
Length of output: 2773
🏁 Script executed:
rg "IndexFundModel|indexFundStore" --type ts -A 2 -B 1Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io
Length of output: 1499
🏁 Script executed:
sed -n '1,80p' ./models/Repository.tsRepository: Open-Source-Bazaar/Open-Source-Bazaar.github.io
Length of output: 1819
🏁 Script executed:
rg "ScrollList|pagination|page|offset" pages/finance/index.tsx -B 2 -A 2Repository: Open-Source-Bazaar/Open-Source-Bazaar.github.io
Length of output: 726
loadPage() receives page parameter but doesn't pass it to the API request
The method signature accepts page but the URL construction omits it:
`${this.baseURI}?${buildURLData({ ...filter, limit })}`This should include the page parameter like the RepositorySearchModel does:
- `${this.baseURI}?${buildURLData({ ...filter, limit })}`,
+ `${this.baseURI}?${buildURLData({ ...filter, page, limit })}`,The ScrollList component expects pagination support. Without passing page, pagination will not function correctly when users scroll and attempt to load additional pages.
🤖 Prompt for AI Agents
In models/Finance.ts around lines 24 to 32, loadPage accepts a page parameter
but the constructed request URL omits it, breaking pagination; update the URL
construction to include the page value in the query (e.g., add page to the
object passed to buildURLData alongside filter and limit) so the API receives
the desired page index and ScrollList pagination works correctly.
Checklist(清单):
freeCodeChengDu 周末编程自习室 WorkShop 作品
2025/11/15
Reviewer changes
Summary by CodeRabbit
新功能
样式
空状态
✏️ Tip: You can customize this high-level summary in your review settings.