1
This commit is contained in:
parent
3fc2d86a46
commit
a6e253a0c5
Binary file not shown.
@ -288,6 +288,7 @@ async def get_recommendation_history(days: int = 7) -> list[dict]:
|
||||
stmt = text(
|
||||
"SELECT * FROM recommendations "
|
||||
"WHERE created_at >= :start "
|
||||
"AND score >= 60 "
|
||||
"AND id IN ("
|
||||
" SELECT MAX(id) FROM recommendations "
|
||||
" WHERE created_at >= :start "
|
||||
@ -426,7 +427,10 @@ async def _save_to_db(result: dict):
|
||||
|
||||
# 保存推荐(按 ts_code 清除当日旧记录,避免同一天多次扫描产生重复)
|
||||
today_str = datetime.now().strftime("%Y-%m-%d")
|
||||
saved_count = 0
|
||||
for rec in result.get("recommendations", []):
|
||||
if rec.score < 60:
|
||||
continue
|
||||
await db.execute(
|
||||
text("DELETE FROM recommendations WHERE date(created_at) = :today AND ts_code = :code"),
|
||||
{"today": today_str, "code": rec.ts_code},
|
||||
@ -456,9 +460,10 @@ async def _save_to_db(result: dict):
|
||||
scan_session=rec.scan_session,
|
||||
)
|
||||
await db.execute(stmt)
|
||||
saved_count += 1
|
||||
|
||||
await db.commit()
|
||||
logger.info(f"已保存 {len(result.get('recommendations', []))} 条推荐到数据库")
|
||||
logger.info(f"已保存 {saved_count} 条推荐到数据库(共 {len(result.get('recommendations', []))} 条,过滤掉 <60 分)")
|
||||
except Exception as e:
|
||||
logger.error(f"保存推荐到数据库失败: {e}")
|
||||
|
||||
|
||||
@ -16,20 +16,10 @@
|
||||
"static/css/app/layout.css",
|
||||
"static/chunks/app/layout.js"
|
||||
],
|
||||
"/(public)/login/page": [
|
||||
"/_not-found/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/(public)/login/page.js"
|
||||
],
|
||||
"/(auth)/dashboard/page": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/(auth)/dashboard/page.js"
|
||||
],
|
||||
"/(auth)/layout": [
|
||||
"static/chunks/webpack.js",
|
||||
"static/chunks/main-app.js",
|
||||
"static/chunks/app/(auth)/layout.js"
|
||||
"static/chunks/app/_not-found/page.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
2
frontend/.next/cache/.tsbuildinfo
vendored
2
frontend/.next/cache/.tsbuildinfo
vendored
File diff suppressed because one or more lines are too long
@ -1,5 +1,4 @@
|
||||
{
|
||||
"/(public)/page": "app/(public)/page.js",
|
||||
"/(public)/login/page": "app/(public)/login/page.js",
|
||||
"/(auth)/dashboard/page": "app/(auth)/dashboard/page.js"
|
||||
"/_not-found/page": "app/_not-found/page.js",
|
||||
"/(public)/page": "app/(public)/page.js"
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"node": {},
|
||||
"edge": {},
|
||||
"encryptionKey": "m7y670mhDOo8SSKub8gVRhrD89+RG50BK5Q4DqLVZ2s="
|
||||
"encryptionKey": "SjG27okSsVCMkYFxh9VzL5dGKrpz1m5ZqXbmUB/f6XQ="
|
||||
}
|
||||
@ -125,7 +125,7 @@
|
||||
/******/
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.h = () => ("de49d7cff76726e2")
|
||||
/******/ __webpack_require__.h = () => ("9f25f13e592044c8")
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -30,21 +30,28 @@ function DataStreamCanvas() {
|
||||
speed: 0.15 + Math.random() * 0.3,
|
||||
code: stockCodes[Math.floor(Math.random() * stockCodes.length)],
|
||||
pct: `${(Math.random() * 6 - 1).toFixed(2)}%`,
|
||||
opacity: 0.03 + Math.random() * 0.06,
|
||||
opacity: 0.08 + Math.random() * 0.12,
|
||||
});
|
||||
}
|
||||
|
||||
let animId: number;
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
|
||||
function resize() {
|
||||
if (!canvas) return;
|
||||
canvas.width = canvas.offsetWidth * 1.5;
|
||||
canvas.height = canvas.offsetHeight * 1.5;
|
||||
if (!canvas || !ctx) return;
|
||||
canvas.width = canvas.offsetWidth * dpr;
|
||||
canvas.height = canvas.offsetHeight * dpr;
|
||||
ctx.scale(dpr, dpr);
|
||||
}
|
||||
resize();
|
||||
window.addEventListener("resize", resize);
|
||||
|
||||
const displayWidth = () => canvas.offsetWidth;
|
||||
const displayHeight = () => canvas.offsetHeight;
|
||||
const fontSize = () => Math.max(10, displayWidth() * 0.012);
|
||||
|
||||
function draw() {
|
||||
if (!ctx || !canvas) return;
|
||||
if (!canvas || !ctx) return;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
streams.forEach((s) => {
|
||||
@ -54,21 +61,21 @@ function DataStreamCanvas() {
|
||||
s.x = Math.random() * 100;
|
||||
s.code = stockCodes[Math.floor(Math.random() * stockCodes.length)];
|
||||
s.pct = `${(Math.random() * 6 - 1).toFixed(2)}%`;
|
||||
s.opacity = 0.03 + Math.random() * 0.06;
|
||||
s.opacity = 0.08 + Math.random() * 0.12;
|
||||
}
|
||||
|
||||
const px = (s.x / 100) * canvas.width;
|
||||
const py = (s.y / 100) * canvas.height;
|
||||
const px = (s.x / 100) * displayWidth();
|
||||
const py = (s.y / 100) * displayHeight();
|
||||
|
||||
ctx.fillStyle = `rgba(251, 191, 36, ${s.opacity})`;
|
||||
ctx.font = `${canvas.width * 0.008}px monospace`;
|
||||
ctx.font = `${fontSize()}px monospace`;
|
||||
ctx.fillText(s.code, px, py);
|
||||
|
||||
const isUp = parseFloat(s.pct) > 0;
|
||||
ctx.fillStyle = isUp
|
||||
? `rgba(255, 107, 107, ${s.opacity * 0.7})`
|
||||
: `rgba(52, 211, 153, ${s.opacity * 0.7})`;
|
||||
ctx.fillText(s.pct, px, py + canvas.width * 0.012);
|
||||
? `rgba(255, 107, 107, ${s.opacity * 0.9})`
|
||||
: `rgba(52, 211, 153, ${s.opacity * 0.9})`;
|
||||
ctx.fillText(s.pct, px, py + fontSize() * 1.3);
|
||||
});
|
||||
|
||||
animId = requestAnimationFrame(draw);
|
||||
@ -85,7 +92,7 @@ function DataStreamCanvas() {
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="absolute inset-0 w-full h-full pointer-events-none"
|
||||
style={{ opacity: 0.6 }}
|
||||
style={{ opacity: 0.8 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -216,11 +223,15 @@ export default function LandingPage() {
|
||||
</div>
|
||||
|
||||
{/* Scroll hint */}
|
||||
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 text-text-muted/40 animate-fade-in-up" style={{ animationDelay: "1.5s" }}>
|
||||
<span className="text-xs">向下滚动</span>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M12 5v14M5 12l7 7 7-7" />
|
||||
</svg>
|
||||
<div className="absolute bottom-8 inset-x-0 flex justify-center animate-fade-in-up" style={{ animationDelay: "1.5s" }}>
|
||||
<div className="flex flex-col items-center gap-2 text-text-muted/60">
|
||||
<span className="text-xs">向下滚动</span>
|
||||
<div className="animate-bounce-down">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M12 5v14M5 12l7 7 7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@ -285,6 +285,15 @@ body::after {
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce-down {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(6px);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in-up {
|
||||
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) both;
|
||||
}
|
||||
@ -302,6 +311,10 @@ body::after {
|
||||
.delay-300 { animation-delay: 300ms; }
|
||||
.delay-375 { animation-delay: 375ms; }
|
||||
|
||||
.animate-bounce-down {
|
||||
animation: bounce-down 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Score bar gradient */
|
||||
.score-bar-gradient-high {
|
||||
background: linear-gradient(90deg, #ff6b6b, #f59e0b);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user