This commit is contained in:
aaron 2026-04-16 22:07:05 +08:00
parent 3fc2d86a46
commit a6e253a0c5
10 changed files with 72 additions and 56 deletions

View File

@ -288,6 +288,7 @@ async def get_recommendation_history(days: int = 7) -> list[dict]:
stmt = text( stmt = text(
"SELECT * FROM recommendations " "SELECT * FROM recommendations "
"WHERE created_at >= :start " "WHERE created_at >= :start "
"AND score >= 60 "
"AND id IN (" "AND id IN ("
" SELECT MAX(id) FROM recommendations " " SELECT MAX(id) FROM recommendations "
" WHERE created_at >= :start " " WHERE created_at >= :start "
@ -426,7 +427,10 @@ async def _save_to_db(result: dict):
# 保存推荐(按 ts_code 清除当日旧记录,避免同一天多次扫描产生重复) # 保存推荐(按 ts_code 清除当日旧记录,避免同一天多次扫描产生重复)
today_str = datetime.now().strftime("%Y-%m-%d") today_str = datetime.now().strftime("%Y-%m-%d")
saved_count = 0
for rec in result.get("recommendations", []): for rec in result.get("recommendations", []):
if rec.score < 60:
continue
await db.execute( await db.execute(
text("DELETE FROM recommendations WHERE date(created_at) = :today AND ts_code = :code"), text("DELETE FROM recommendations WHERE date(created_at) = :today AND ts_code = :code"),
{"today": today_str, "code": rec.ts_code}, {"today": today_str, "code": rec.ts_code},
@ -456,9 +460,10 @@ async def _save_to_db(result: dict):
scan_session=rec.scan_session, scan_session=rec.scan_session,
) )
await db.execute(stmt) await db.execute(stmt)
saved_count += 1
await db.commit() await db.commit()
logger.info(f"已保存 {len(result.get('recommendations', []))}推荐到数据库") logger.info(f"已保存 {saved_count} 条推荐到数据库(共 {len(result.get('recommendations', []))},过滤掉 <60 分)")
except Exception as e: except Exception as e:
logger.error(f"保存推荐到数据库失败: {e}") logger.error(f"保存推荐到数据库失败: {e}")

View File

@ -16,20 +16,10 @@
"static/css/app/layout.css", "static/css/app/layout.css",
"static/chunks/app/layout.js" "static/chunks/app/layout.js"
], ],
"/(public)/login/page": [ "/_not-found/page": [
"static/chunks/webpack.js", "static/chunks/webpack.js",
"static/chunks/main-app.js", "static/chunks/main-app.js",
"static/chunks/app/(public)/login/page.js" "static/chunks/app/_not-found/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"
] ]
} }
} }

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,4 @@
{ {
"/(public)/page": "app/(public)/page.js", "/_not-found/page": "app/_not-found/page.js",
"/(public)/login/page": "app/(public)/login/page.js", "/(public)/page": "app/(public)/page.js"
"/(auth)/dashboard/page": "app/(auth)/dashboard/page.js"
} }

View File

@ -1,5 +1,5 @@
{ {
"node": {}, "node": {},
"edge": {}, "edge": {},
"encryptionKey": "m7y670mhDOo8SSKub8gVRhrD89+RG50BK5Q4DqLVZ2s=" "encryptionKey": "SjG27okSsVCMkYFxh9VzL5dGKrpz1m5ZqXbmUB/f6XQ="
} }

View File

@ -125,7 +125,7 @@
/******/ /******/
/******/ /* webpack/runtime/getFullHash */ /******/ /* webpack/runtime/getFullHash */
/******/ (() => { /******/ (() => {
/******/ __webpack_require__.h = () => ("de49d7cff76726e2") /******/ __webpack_require__.h = () => ("9f25f13e592044c8")
/******/ })(); /******/ })();
/******/ /******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ /* webpack/runtime/hasOwnProperty shorthand */

File diff suppressed because one or more lines are too long

View File

@ -30,21 +30,28 @@ function DataStreamCanvas() {
speed: 0.15 + Math.random() * 0.3, speed: 0.15 + Math.random() * 0.3,
code: stockCodes[Math.floor(Math.random() * stockCodes.length)], code: stockCodes[Math.floor(Math.random() * stockCodes.length)],
pct: `${(Math.random() * 6 - 1).toFixed(2)}%`, pct: `${(Math.random() * 6 - 1).toFixed(2)}%`,
opacity: 0.03 + Math.random() * 0.06, opacity: 0.08 + Math.random() * 0.12,
}); });
} }
let animId: number; let animId: number;
const dpr = window.devicePixelRatio || 1;
function resize() { function resize() {
if (!canvas) return; if (!canvas || !ctx) return;
canvas.width = canvas.offsetWidth * 1.5; canvas.width = canvas.offsetWidth * dpr;
canvas.height = canvas.offsetHeight * 1.5; canvas.height = canvas.offsetHeight * dpr;
ctx.scale(dpr, dpr);
} }
resize(); resize();
window.addEventListener("resize", resize); window.addEventListener("resize", resize);
const displayWidth = () => canvas.offsetWidth;
const displayHeight = () => canvas.offsetHeight;
const fontSize = () => Math.max(10, displayWidth() * 0.012);
function draw() { function draw() {
if (!ctx || !canvas) return; if (!canvas || !ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
streams.forEach((s) => { streams.forEach((s) => {
@ -54,21 +61,21 @@ function DataStreamCanvas() {
s.x = Math.random() * 100; s.x = Math.random() * 100;
s.code = stockCodes[Math.floor(Math.random() * stockCodes.length)]; s.code = stockCodes[Math.floor(Math.random() * stockCodes.length)];
s.pct = `${(Math.random() * 6 - 1).toFixed(2)}%`; 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 px = (s.x / 100) * displayWidth();
const py = (s.y / 100) * canvas.height; const py = (s.y / 100) * displayHeight();
ctx.fillStyle = `rgba(251, 191, 36, ${s.opacity})`; 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); ctx.fillText(s.code, px, py);
const isUp = parseFloat(s.pct) > 0; const isUp = parseFloat(s.pct) > 0;
ctx.fillStyle = isUp ctx.fillStyle = isUp
? `rgba(255, 107, 107, ${s.opacity * 0.7})` ? `rgba(255, 107, 107, ${s.opacity * 0.9})`
: `rgba(52, 211, 153, ${s.opacity * 0.7})`; : `rgba(52, 211, 153, ${s.opacity * 0.9})`;
ctx.fillText(s.pct, px, py + canvas.width * 0.012); ctx.fillText(s.pct, px, py + fontSize() * 1.3);
}); });
animId = requestAnimationFrame(draw); animId = requestAnimationFrame(draw);
@ -85,7 +92,7 @@ function DataStreamCanvas() {
<canvas <canvas
ref={canvasRef} ref={canvasRef}
className="absolute inset-0 w-full h-full pointer-events-none" 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> </div>
{/* Scroll hint */} {/* 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" }}> <div className="absolute bottom-8 inset-x-0 flex justify-center animate-fade-in-up" style={{ animationDelay: "1.5s" }}>
<span className="text-xs"></span> <div className="flex flex-col items-center gap-2 text-text-muted/60">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"> <span className="text-xs"></span>
<path d="M12 5v14M5 12l7 7 7-7" /> <div className="animate-bounce-down">
</svg> <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> </div>
</section> </section>

View File

@ -285,6 +285,15 @@ body::after {
} }
} }
@keyframes bounce-down {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(6px);
}
}
.animate-fade-in-up { .animate-fade-in-up {
animation: fadeInUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) both; 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-300 { animation-delay: 300ms; }
.delay-375 { animation-delay: 375ms; } .delay-375 { animation-delay: 375ms; }
.animate-bounce-down {
animation: bounce-down 2s ease-in-out infinite;
}
/* Score bar gradient */ /* Score bar gradient */
.score-bar-gradient-high { .score-bar-gradient-high {
background: linear-gradient(90deg, #ff6b6b, #f59e0b); background: linear-gradient(90deg, #ff6b6b, #f59e0b);