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(
|
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}")
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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",
|
"/_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"
|
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"node": {},
|
"node": {},
|
||||||
"edge": {},
|
"edge": {},
|
||||||
"encryptionKey": "m7y670mhDOo8SSKub8gVRhrD89+RG50BK5Q4DqLVZ2s="
|
"encryptionKey": "SjG27okSsVCMkYFxh9VzL5dGKrpz1m5ZqXbmUB/f6XQ="
|
||||||
}
|
}
|
||||||
@ -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
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user