@@ -409,9 +339,9 @@ export default function DcaModal({ fund, plan, onClose, onConfirm }) {
diff --git a/app/components/FundIntradayChart.jsx b/app/components/FundIntradayChart.jsx
index e371446..4a49c03 100644
--- a/app/components/FundIntradayChart.jsx
+++ b/app/components/FundIntradayChart.jsx
@@ -22,14 +22,41 @@ ChartJS.register(
Filler
);
+const CHART_COLORS = {
+ dark: {
+ danger: '#f87171',
+ success: '#34d399',
+ primary: '#22d3ee',
+ muted: '#9ca3af',
+ border: '#1f2937',
+ text: '#e5e7eb',
+ crosshairText: '#0f172a',
+ },
+ light: {
+ danger: '#dc2626',
+ success: '#059669',
+ primary: '#0891b2',
+ muted: '#475569',
+ border: '#e2e8f0',
+ text: '#0f172a',
+ crosshairText: '#ffffff',
+ }
+};
+
+function getChartThemeColors(theme) {
+ return CHART_COLORS[theme] || CHART_COLORS.dark;
+}
+
/**
* 分时图:展示当日(或最近一次记录日)的估值序列,纵轴为相对参考净值的涨跌幅百分比。
* series: Array<{ time: string, value: number, date?: string }>
* referenceNav: 参考净值(最新单位净值),用于计算涨跌幅;未传则用当日第一个估值作为参考。
+ * theme: 'light' | 'dark',用于亮色主题下坐标轴与 crosshair 样式
*/
-export default function FundIntradayChart({ series = [], referenceNav }) {
+export default function FundIntradayChart({ series = [], referenceNav, theme = 'dark' }) {
const chartRef = useRef(null);
const hoverTimeoutRef = useRef(null);
+ const chartColors = useMemo(() => getChartThemeColors(theme), [theme]);
const chartData = useMemo(() => {
if (!series.length) return { labels: [], datasets: [] };
@@ -40,9 +67,8 @@ export default function FundIntradayChart({ series = [], referenceNav }) {
: values[0];
const percentages = values.map((v) => (ref ? ((v - ref) / ref) * 100 : 0));
const lastPct = percentages[percentages.length - 1];
- const riseColor = '#f87171'; // 涨用红色
- const fallColor = '#34d399'; // 跌用绿色
- // 以最新点相对参考净值的涨跌定色:涨(>=0)红,跌(<0)绿
+ const riseColor = chartColors.danger;
+ const fallColor = chartColors.success;
const lineColor = lastPct != null && lastPct >= 0 ? riseColor : fallColor;
return {
@@ -68,9 +94,11 @@ export default function FundIntradayChart({ series = [], referenceNav }) {
}
]
};
- }, [series, referenceNav]);
+ }, [series, referenceNav, chartColors.danger, chartColors.success]);
- const options = useMemo(() => ({
+ const options = useMemo(() => {
+ const colors = getChartThemeColors();
+ return {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
@@ -88,7 +116,7 @@ export default function FundIntradayChart({ series = [], referenceNav }) {
display: true,
grid: { display: false },
ticks: {
- color: '#9ca3af',
+ color: colors.muted,
font: { size: 10 },
maxTicksLimit: 6
}
@@ -96,9 +124,9 @@ export default function FundIntradayChart({ series = [], referenceNav }) {
y: {
display: true,
position: 'left',
- grid: { color: '#1f2937', drawBorder: false },
+ grid: { color: colors.border, drawBorder: false },
ticks: {
- color: '#9ca3af',
+ color: colors.muted,
font: { size: 10 },
callback: (v) => (isNumber(v) ? `${v >= 0 ? '+' : ''}${v.toFixed(2)}%` : v)
}
@@ -142,7 +170,8 @@ export default function FundIntradayChart({ series = [], referenceNav }) {
}, 2000);
}
}
- }), []);
+ };
+ }, [theme]);
useEffect(() => {
return () => {
@@ -152,7 +181,9 @@ export default function FundIntradayChart({ series = [], referenceNav }) {
};
}, []);
- const plugins = useMemo(() => [{
+ const plugins = useMemo(() => {
+ const colors = getChartThemeColors(theme);
+ return [{
id: 'crosshair',
afterDraw: (chart) => {
const ctx = chart.ctx;
@@ -175,17 +206,15 @@ export default function FundIntradayChart({ series = [], referenceNav }) {
ctx.save();
ctx.setLineDash([3, 3]);
ctx.lineWidth = 1;
- ctx.strokeStyle = '#9ca3af';
+ ctx.strokeStyle = colors.muted;
ctx.moveTo(x, topY);
ctx.lineTo(x, bottomY);
ctx.moveTo(leftX, y);
ctx.lineTo(rightX, y);
ctx.stroke();
- const prim = typeof document !== 'undefined'
- ? (getComputedStyle(document.documentElement).getPropertyValue('--primary').trim() || '#22d3ee')
- : '#22d3ee';
- const bgText = '#0f172a';
+ const prim = colors.primary;
+ const textCol = colors.crosshairText;
ctx.font = '10px sans-serif';
ctx.textAlign = 'center';
@@ -202,7 +231,7 @@ export default function FundIntradayChart({ series = [], referenceNav }) {
const labelCenterX = labelLeft + tw / 2;
ctx.fillStyle = prim;
ctx.fillRect(labelLeft, bottomY, tw, 16);
- ctx.fillStyle = bgText;
+ ctx.fillStyle = textCol;
ctx.fillText(timeStr, labelCenterX, bottomY + 8);
}
if (data && index in data) {
@@ -211,12 +240,13 @@ export default function FundIntradayChart({ series = [], referenceNav }) {
const vw = ctx.measureText(valueStr).width + 8;
ctx.fillStyle = prim;
ctx.fillRect(leftX, y - 8, vw, 16);
- ctx.fillStyle = bgText;
+ ctx.fillStyle = textCol;
ctx.fillText(valueStr, leftX + vw / 2, y);
}
ctx.restore();
}
- }], []);
+ }];
+ }, [theme]);
if (series.length < 2) return null;
diff --git a/app/components/FundTrendChart.jsx b/app/components/FundTrendChart.jsx
index 08d42c4..4c36db1 100644
--- a/app/components/FundTrendChart.jsx
+++ b/app/components/FundTrendChart.jsx
@@ -29,7 +29,32 @@ ChartJS.register(
Filler
);
-export default function FundTrendChart({ code, isExpanded, onToggleExpand, transactions = [] }) {
+const CHART_COLORS = {
+ dark: {
+ danger: '#f87171',
+ success: '#34d399',
+ primary: '#22d3ee',
+ muted: '#9ca3af',
+ border: '#1f2937',
+ text: '#e5e7eb',
+ crosshairText: '#0f172a',
+ },
+ light: {
+ danger: '#dc2626',
+ success: '#059669',
+ primary: '#0891b2',
+ muted: '#475569',
+ border: '#e2e8f0',
+ text: '#0f172a',
+ crosshairText: '#ffffff',
+ }
+};
+
+function getChartThemeColors(theme) {
+ return CHART_COLORS[theme] || CHART_COLORS.dark;
+}
+
+export default function FundTrendChart({ code, isExpanded, onToggleExpand, transactions = [], theme = 'dark' }) {
const [range, setRange] = useState('1m');
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
@@ -37,6 +62,8 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
const chartRef = useRef(null);
const hoverTimeoutRef = useRef(null);
+ const chartColors = useMemo(() => getChartThemeColors(theme), [theme]);
+
useEffect(() => {
// If collapsed, don't fetch data unless we have no data yet
if (!isExpanded && data.length > 0) return;
@@ -84,12 +111,11 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
return ((last - first) / first) * 100;
}, [data]);
- // Red for up, Green for down (CN market style)
- // Hardcoded hex values from globals.css for Chart.js
- const upColor = '#f87171'; // --danger,与折线图红色一致
- const downColor = '#34d399'; // --success
+ // Red for up, Green for down (CN market style),随主题使用 CSS 变量
+ const upColor = chartColors.danger;
+ const downColor = chartColors.success;
const lineColor = change >= 0 ? upColor : downColor;
- const primaryColor = typeof document !== 'undefined' ? (getComputedStyle(document.documentElement).getPropertyValue('--primary').trim() || '#22d3ee') : '#22d3ee';
+ const primaryColor = chartColors.primary;
const chartData = useMemo(() => {
// Calculate percentage change based on the first data point
@@ -165,9 +191,10 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
}
]
};
- }, [data, lineColor, transactions, primaryColor]);
+ }, [data, transactions, lineColor, primaryColor, upColor]);
const options = useMemo(() => {
+ const colors = getChartThemeColors(theme);
return {
responsive: true,
maintainAspectRatio: false,
@@ -190,7 +217,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
drawBorder: false
},
ticks: {
- color: '#9ca3af',
+ color: colors.muted,
font: { size: 10 },
maxTicksLimit: 4,
maxRotation: 0
@@ -201,12 +228,12 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
display: true,
position: 'left',
grid: {
- color: '#1f2937',
+ color: colors.border,
drawBorder: false,
tickLength: 0
},
ticks: {
- color: '#9ca3af',
+ color: colors.muted,
font: { size: 10 },
count: 5,
callback: (value) => `${value.toFixed(2)}%`
@@ -240,7 +267,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
},
onClick: () => {}
};
- }, []);
+ }, [theme]);
useEffect(() => {
return () => {
@@ -250,7 +277,9 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
};
}, []);
- const plugins = useMemo(() => [{
+ const plugins = useMemo(() => {
+ const colors = getChartThemeColors(theme);
+ return [{
id: 'crosshair',
afterEvent: (chart, args) => {
const { event, replay } = args || {};
@@ -276,7 +305,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
afterDraw: (chart) => {
const ctx = chart.ctx;
const datasets = chart.data.datasets;
- const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary').trim() || '#22d3ee';
+ const primaryColor = colors.primary;
// 绘制圆角矩形(兼容无 roundRect 的环境)
const drawRoundRect = (left, top, w, h, r) => {
@@ -377,7 +406,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
ctx.beginPath();
ctx.setLineDash([3, 3]);
ctx.lineWidth = 1;
- ctx.strokeStyle = '#9ca3af';
+ ctx.strokeStyle = colors.muted;
// Draw vertical line
ctx.moveTo(x, topY);
@@ -415,7 +444,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
const labelCenterX = labelLeft + textWidth / 2;
ctx.fillStyle = primaryColor;
ctx.fillRect(labelLeft, bottomY, textWidth, 16);
- ctx.fillStyle = '#0f172a'; // --background
+ ctx.fillStyle = colors.crosshairText;
ctx.fillText(dateStr, labelCenterX, bottomY + 8);
// Y axis label (value)
@@ -423,7 +452,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
const valWidth = ctx.measureText(valueStr).width + 8;
ctx.fillStyle = primaryColor;
ctx.fillRect(leftX, y - 8, valWidth, 16);
- ctx.fillStyle = '#0f172a'; // --background
+ ctx.fillStyle = colors.crosshairText;
ctx.textAlign = 'center';
ctx.fillText(valueStr, leftX + valWidth / 2, y);
}
@@ -442,7 +471,7 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
const label = datasets[dsIndex].label;
// Determine background color based on dataset index
// 1 = Buy (主题色), 2 = Sell (与折线图红色一致)
- const bgColor = dsIndex === 1 ? primaryColor : '#f87171';
+ const bgColor = dsIndex === 1 ? primaryColor : colors.danger;
// If collision, offset Buy label upwards
let yOffset = 0;
@@ -457,7 +486,8 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
ctx.restore();
}
}
- }], []); // 移除 data 依赖,因为我们直接从 chart 实例读取数据
+ }];
+ }, [theme]); // theme 变化时重算以应用亮色/暗色坐标轴与 crosshair
return (
e.stopPropagation()}>
@@ -501,19 +531,13 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
>
{loading && (
-
+
加载中...
)}
{!loading && data.length === 0 && (
-
+
暂无数据
)}
@@ -523,23 +547,13 @@ export default function FundTrendChart({ code, isExpanded, onToggleExpand, trans
)}
-
+
{ranges.map(r => (
diff --git a/app/components/HoldingActionModal.jsx b/app/components/HoldingActionModal.jsx
index b700fab..1401c3b 100644
--- a/app/components/HoldingActionModal.jsx
+++ b/app/components/HoldingActionModal.jsx
@@ -75,9 +75,9 @@ export default function HoldingActionModal({ fund, onClose, onAction, hasHistory
减仓
diff --git a/app/components/Icons.jsx b/app/components/Icons.jsx
index 7aa8186..15203f6 100644
--- a/app/components/Icons.jsx
+++ b/app/components/Icons.jsx
@@ -243,3 +243,20 @@ export function CameraIcon(props) {
);
}
+
+export function SunIcon(props) {
+ return (
+
+ );
+}
+
+export function MoonIcon(props) {
+ return (
+
+ );
+}
diff --git a/app/components/PcFundTable.jsx b/app/components/PcFundTable.jsx
index 2ce4d51..7835d56 100644
--- a/app/components/PcFundTable.jsx
+++ b/app/components/PcFundTable.jsx
@@ -564,7 +564,7 @@ export default function PcFundTable({
left: isLeft ? `${column.getStart('left')}px` : undefined,
right: isRight ? `${column.getAfter('right')}px` : undefined,
zIndex: isHeader ? 11 : 10,
- backgroundColor: isHeader ? '#2a394b' : 'var(--row-bg)',
+ backgroundColor: isHeader ? 'var(--table-pinned-header-bg)' : 'var(--row-bg)',
boxShadow: 'none',
textAlign: isNameColumn ? 'left' : 'center',
justifyContent: isNameColumn ? 'flex-start' : 'center',
@@ -572,14 +572,14 @@ export default function PcFundTable({
};
return (
- <>
+