style(portfolio): 优化 CSV 导出字段格式,精准剥离价格末尾的无意义零

This commit is contained in:
kennethcheng 2026-05-02 15:19:48 +08:00
parent 3e81c1dc5b
commit e692d47b6a
2 changed files with 17 additions and 2 deletions

View File

@ -1,5 +1,10 @@
# Omniledger 架构与开发记忆 (Memory) # Omniledger 架构与开发记忆 (Memory)
## 优化 exportToCSV 数据净洗逻辑,利用防科学计数法的纯正则处理去除现价/成本价末尾的无意义零 (Task 69)
- 在 `app/dashboard/page.tsx``exportToCSV()` 函数顶部注入 `stripTrailingZeros` 纯字符串去零工具函数。
- 该函数内置防御性设计:对 null/undefined/空值返回 `"0"`;仅对包含小数点的字符串执行正则处理(`/0+$/` 剥离尾随零 → `/\.$/` 剥离末尾小数点),彻底杜绝极小数值(如 `0.00000001`)被 `String()` 转换后触发科学计数法(`1e-8`)。
- 将 CSV 映射层中的"成本价"字段(`avgCostNative`)和"现价"字段(`latestPrice`)包裹 `stripTrailingZeros()`,使小米 `29.020` 输出为 `29.02`、整数价格 `29.00` 输出为 `29`,而"总市值""浮动盈亏""累计盈亏"等法币资产字段保留 `.toFixed(2)` 的两位小数格式以维持表格对齐。
## 新增持仓明细的已清仓资产显示开关(基于 1e-8 精度容差过滤),并实装注入 UTF-8 BOM 的客户端 CSV 导出功能 (Task 68) ## 新增持仓明细的已清仓资产显示开关(基于 1e-8 精度容差过滤),并实装注入 UTF-8 BOM 的客户端 CSV 导出功能 (Task 68)
- 在 `src/actions/portfolio.ts``getPortfolioPositions()` 函数中新增 `includeCleared: boolean = false` 参数,将清仓判定从 `quantity === 0` 升级为 `Big.js``1e-8` 精度容差过滤(`holding.quantity.gt('1e-8')`),杜绝浮点数灰尘资产被错误保留或隐藏。 - 在 `src/actions/portfolio.ts``getPortfolioPositions()` 函数中新增 `includeCleared: boolean = false` 参数,将清仓判定从 `quantity === 0` 升级为 `Big.js``1e-8` 精度容差过滤(`holding.quantity.gt('1e-8')`),杜绝浮点数灰尘资产被错误保留或隐藏。
- 当 `!includeCleared` 时,自动过滤掉持仓量 ≤ 1e-8 的已清仓资产;当 `includeCleared` 为 true 时,已清仓资产会被返回,其 `marketValue``floatingPnl` 为 0但**保留真实计算出的 `accumulatedPnl`(累计盈亏)字段**,确保历史盈亏数据不丢失。 - 当 `!includeCleared` 时,自动过滤掉持仓量 ≤ 1e-8 的已清仓资产;当 `includeCleared` 为 true 时,已清仓资产会被返回,其 `marketValue``floatingPnl` 为 0但**保留真实计算出的 `accumulatedPnl`(累计盈亏)字段**,确保历史盈亏数据不丢失。

View File

@ -56,14 +56,24 @@ function formatNative(value: string, baseCurrency: string): string {
} }
function exportToCSV(positions: any[]) { function exportToCSV(positions: any[]) {
const stripTrailingZeros = (val: any): string => {
if (val === null || val === undefined || val === '') return "0";
let str = String(val);
if (str.includes('.')) {
str = str.replace(/0+$/, '');
str = str.replace(/\.$/, '');
}
return str;
};
const headers = ["资产名称", "代码", "持仓量", "成本价", "现价", "总市值", "浮动盈亏", "累计盈亏"]; const headers = ["资产名称", "代码", "持仓量", "成本价", "现价", "总市值", "浮动盈亏", "累计盈亏"];
const rows = positions.map(item => [ const rows = positions.map(item => [
item.name || item.symbol, item.name || item.symbol,
item.symbol, item.symbol,
item.quantity || '0', item.quantity || '0',
new Big(item.avgCostNative || '0').toFixed(2), stripTrailingZeros(new Big(item.avgCostNative || '0').toFixed(2)),
item.latestPrice || '0', stripTrailingZeros(item.latestPrice || '0'),
new Big(item.marketValueNative || '0').toFixed(2), new Big(item.marketValueNative || '0').toFixed(2),
new Big(item.floatingPnlNative || '0').toFixed(2), new Big(item.floatingPnlNative || '0').toFixed(2),
new Big(item.cumulativePnlNative || '0').toFixed(2), new Big(item.cumulativePnlNative || '0').toFixed(2),