feat: add initial frontend setup with styles, types, and testing framework
- Created a global CSS file for styling the frontend with responsive design. - Introduced TypeScript types for various entities including Research, Alpha, and Account. - Implemented Playwright tests for account management and data synchronization workflows. - Configured TypeScript with strict settings and included necessary libraries. - Set up Vite as the build tool with React plugin and API proxy configuration. - Added a Python script to initialize environment variables securely.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import type { Pnl } from "../types";
|
||||
|
||||
export function PnlChart({ points }: { points: Pnl["points"] }) {
|
||||
const values = points
|
||||
.map((p) => p.value)
|
||||
.filter((v): v is number => v !== null && Number.isFinite(v));
|
||||
if (!values.length)
|
||||
return <div className="chart-empty">暂无可绘制的 PnL 数据</div>;
|
||||
const low = Math.min(...values),
|
||||
high = Math.max(...values),
|
||||
range = high - low || Math.max(1, Math.abs(high) * 0.1);
|
||||
const x = (index: number) =>
|
||||
62 + (index / Math.max(1, points.length - 1)) * 610;
|
||||
const y = (v: number) => 24 + ((high - v) / range) * 180;
|
||||
let segments = "";
|
||||
let connected = false;
|
||||
points.forEach((point, i) => {
|
||||
if (point.value === null) {
|
||||
connected = false;
|
||||
return;
|
||||
}
|
||||
segments += `${connected ? " L" : " M"}${x(i)} ${y(point.value)}`;
|
||||
connected = true;
|
||||
});
|
||||
return (
|
||||
<svg
|
||||
className="pnl-chart"
|
||||
viewBox="0 0 700 240"
|
||||
role="img"
|
||||
aria-label={`PnL 曲线,共 ${points.length} 条记录`}
|
||||
>
|
||||
{[0, 1, 2, 3].map((i) => (
|
||||
<g key={i}>
|
||||
<line
|
||||
x1="62"
|
||||
x2="672"
|
||||
y1={24 + i * 60}
|
||||
y2={24 + i * 60}
|
||||
stroke="var(--line)"
|
||||
/>
|
||||
<text x="52" y={28 + i * 60} textAnchor="end">
|
||||
{(high - (i * range) / 3).toLocaleString(undefined, {
|
||||
maximumFractionDigits: 0,
|
||||
})}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
<path d={segments} fill="none" stroke="var(--accent)" strokeWidth="2" />
|
||||
{points.length === 1 && points[0].value !== null && (
|
||||
<circle cx={x(0)} cy={y(points[0].value)} r="3" fill="var(--accent)" />
|
||||
)}
|
||||
<text x="62" y="230">
|
||||
{points[0]?.date.slice(0, 10)}
|
||||
</text>
|
||||
<text x="672" y="230" textAnchor="end">
|
||||
{points.at(-1)?.date.slice(0, 10)}
|
||||
</text>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user