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(
"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}")

View File

@ -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"
]
}
}

File diff suppressed because one or more lines are too long

View File

@ -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"
}

View File

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

View File

@ -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

View File

@ -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>

View File

@ -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);