feat: 新增思想实验室模块与配套功能

- 新增5个物理仿真实验室:黑洞模拟、日地轨道、3D太阳系、π推导、拉格朗日点
- 新增思想实验室后端路由与鉴权逻辑
- 首页新增思想实验室页签与前端渲染逻辑
- 修复中韩半导体监控标的配置错误
- 新增市场技能推送白名单过滤逻辑与单元测试
This commit is contained in:
yangxiangyuan
2026-08-06 13:39:48 +08:00
parent 6a7a2b089a
commit ea379116cc
19 changed files with 2831 additions and 17 deletions
@@ -0,0 +1,248 @@
<!-- ============================================================
思想实验室 · labs/sun_earth_orbit · 日地轨道
太阳-地球运行轨迹 2D 动画,演示开普勒第二定律。
独立 lab:单文件自包含,不引用其他 lab 的任何资源。
服务端配置:src/server/thought_lab/labs/sun_earth_orbit/config.json
============================================================ -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>太阳-地球运行轨迹动画</title>
<style>
* { margin: 0; padding: 0; }
html, body { width: 100%; height: 100%; background: #05070d; overflow: hidden; }
canvas { display: block; width: 100%; height: 100%; }
#hud {
position: absolute; top: 12px; left: 14px;
color: #8fa0bd; font: 12px/1.7 "PingFang SC", "Microsoft YaHei", sans-serif;
background: rgba(10,14,26,0.55); padding: 8px 12px; border-radius: 8px;
border: 1px solid rgba(255,255,255,0.08); pointer-events: none;
}
#controls {
position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%);
display: flex; gap: 10px; align-items: center;
background: rgba(10,14,26,0.7); padding: 10px 16px; border-radius: 12px;
border: 1px solid rgba(255,255,255,0.10);
font: 13px "PingFang SC", "Microsoft YaHei", sans-serif; color: #c5cfe0;
}
#controls button {
background: rgba(143,179,255,0.15); border: 1px solid rgba(143,179,255,0.4);
color: #d8e5ff; padding: 6px 14px; border-radius: 8px; cursor: pointer; font-size: 13px;
}
#controls button:hover { background: rgba(143,179,255,0.3); }
#controls input[type=range] { width: 110px; accent-color: #8fb3ff; }
label { display: flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
</style>
</head>
<body>
<canvas id="cv"></canvas>
<div id="hud">
地球公转 · 开普勒第二定律(近日点快,远日点慢)<br>
<span id="stat"></span><br>
<span style="color:#e8b84a">⚠ 演示动画:轨道偏心率放大至 e=0.35(真实 0.0167),非真实扁率</span>
</div>
<div id="controls">
<button id="btnPause">暂停</button>
<span>速度</span><input type="range" id="speed" min="0.2" max="5" step="0.1" value="1">
<label><input type="checkbox" id="showOrbit" checked> 轨道线</label>
<label><input type="checkbox" id="showTrail" checked> 拖尾</label>
</div>
<script>
const cv = document.getElementById('cv');
const ctx = cv.getContext('2d');
let W = 0, H = 0, cx = 0, cy = 0;
function resize() {
// iframe 内 clientWidth 可能在脚本执行时为 0,优先用视口尺寸
const w = window.innerWidth || document.documentElement.clientWidth || 800;
const h = window.innerHeight || document.documentElement.clientHeight || 600;
W = cv.width = Math.max(1, Math.floor(w * devicePixelRatio));
H = cv.height = Math.max(1, Math.floor(h * devicePixelRatio));
cx = W / 2; cy = H / 2;
ctx.setTransform(1, 0, 0, 1, 0, 0);
buildStars();
}
window.addEventListener('resize', resize);
if (window.ResizeObserver) {
new ResizeObserver(resize).observe(document.documentElement);
}
// ---- 星空背景 ----
let stars = [];
function buildStars() {
stars = [];
const n = Math.floor(W * H / 9000);
for (let i = 0; i < n; i++) {
stars.push({
x: Math.random() * W, y: Math.random() * H,
r: Math.random() * 1.2 * devicePixelRatio + 0.3,
a: Math.random() * 0.6 + 0.15,
tw: Math.random() * 2 + 1
});
}
}
// ---- 轨道参数(椭圆,e 夸张化便于观察)----
const ecc = 0.35; // 离心率(真实 0.017,夸张展示)
let baseA = 0; // 半长轴,resize 时计算
let sunR = 0, earthR = 0, moonOrbit = 0, moonR = 0;
function layout() {
baseA = Math.min(W, H) * 0.36;
sunR = Math.min(W, H) * 0.055;
earthR = sunR * 0.32;
moonOrbit = earthR * 2.6;
moonR = earthR * 0.28;
}
// ---- 开普勒运动:用真近点角近似,近日点加速 ----
let M = 0; // 平近点角
function keplerPos(theta) {
// r = a(1-e²)/(1+e·cosθ)
const r = baseA * (1 - ecc * ecc) / (1 + ecc * Math.cos(theta));
return { r, x: r * Math.cos(theta), y: r * Math.sin(theta) };
}
function nextTheta(theta) {
// 角速度 ∝ 1/r²(角动量守恒 → 开普勒第二定律)
const r = baseA * (1 - ecc * ecc) / (1 + ecc * Math.cos(theta));
const dTheta = 0.010 * Math.pow(baseA / r, 2);
return theta + dTheta;
}
let theta = Math.PI; // 从近日点出发
let moonAng = 0;
let paused = false;
let speedMul = 1;
const trail = [];
const TRAIL_MAX = 260;
// ---- 绘制 ----
function draw(t) {
ctx.setTransform(1, 0, 0, 1, 0, 0);
// 深空底色
ctx.fillStyle = '#05070d';
ctx.fillRect(0, 0, W, H);
// 星空(闪烁)
for (const s of stars) {
const tw = 0.6 + 0.4 * Math.sin(t * 0.001 * s.tw + s.x);
ctx.globalAlpha = s.a * tw;
ctx.fillStyle = '#cdd8ee';
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, 7);
ctx.fill();
}
ctx.globalAlpha = 1;
layout();
// 太阳焦点偏移:椭圆焦点在原点,太阳在焦点上,轨道中心偏移 a·e
const ocx = cx + baseA * ecc; // 轨道几何中心
const ocy = cy;
// 轨道线
if (document.getElementById('showOrbit').checked) {
ctx.strokeStyle = 'rgba(140,160,200,0.30)';
ctx.lineWidth = 1 * devicePixelRatio;
ctx.setLineDash([6 * devicePixelRatio, 6 * devicePixelRatio]);
ctx.beginPath();
ctx.ellipse(ocx, ocy, baseA, baseA * Math.sqrt(1 - ecc * ecc), 0, 0, 7);
ctx.stroke();
ctx.setLineDash([]);
}
// 位置(太阳位于原点=焦点 cx,cy)
const p = keplerPos(theta);
const ex = cx + p.x, ey = cy + p.y;
// 拖尾
if (document.getElementById('showTrail').checked) {
trail.push({ x: ex, y: ey });
if (trail.length > TRAIL_MAX) trail.shift();
for (let i = 1; i < trail.length; i++) {
const k = i / trail.length;
ctx.strokeStyle = `rgba(96,165,250,${k * 0.55})`;
ctx.lineWidth = 1.6 * k * devicePixelRatio;
ctx.beginPath();
ctx.moveTo(trail[i - 1].x, trail[i - 1].y);
ctx.lineTo(trail[i].x, trail[i].y);
ctx.stroke();
}
} else trail.length = 0;
// 太阳(光晕)
const glow = ctx.createRadialGradient(cx, cy, 0, cx, cy, sunR * 4);
glow.addColorStop(0, 'rgba(255,200,80,0.9)');
glow.addColorStop(0.25, 'rgba(255,160,50,0.35)');
glow.addColorStop(1, 'rgba(255,140,40,0)');
ctx.fillStyle = glow;
ctx.beginPath();
ctx.arc(cx, cy, sunR * 4, 0, 7);
ctx.fill();
const sunBody = ctx.createRadialGradient(cx - sunR*0.3, cy - sunR*0.3, sunR*0.1, cx, cy, sunR);
sunBody.addColorStop(0, '#fff7d6');
sunBody.addColorStop(0.6, '#ffd75e');
sunBody.addColorStop(1, '#ff9d3c');
ctx.fillStyle = sunBody;
ctx.beginPath();
ctx.arc(cx, cy, sunR, 0, 7);
ctx.fill();
// 地球
const eg = ctx.createRadialGradient(ex - earthR*0.35, ey - earthR*0.35, earthR*0.1, ex, ey, earthR);
eg.addColorStop(0, '#9ed2ff');
eg.addColorStop(0.55, '#3b82d9');
eg.addColorStop(1, '#1b4a8f');
ctx.fillStyle = eg;
ctx.beginPath();
ctx.arc(ex, ey, earthR, 0, 7);
ctx.fill();
// 月球
const mx = ex + moonOrbit * Math.cos(moonAng);
const my = ey + moonOrbit * Math.sin(moonAng);
ctx.fillStyle = '#c9ccd4';
ctx.beginPath();
ctx.arc(mx, my, moonR, 0, 7);
ctx.fill();
// 月球轨道淡线
ctx.strokeStyle = 'rgba(200,205,215,0.15)';
ctx.lineWidth = devicePixelRatio * 0.7;
ctx.beginPath();
ctx.arc(ex, ey, moonOrbit, 0, 7);
ctx.stroke();
// 状态(防御除零)
const rNow = p.r;
const peri = baseA * (1 - ecc), apo = baseA * (1 + ecc);
const span = apo - peri;
const phase = span > 0 ? (rNow - peri) / span : 0.5; // 0=近日点 1=远日点
// 真实日地距离:按同一真近点角、用真实偏心率 0.0167 计算(地球轨道 a = 1 AU)
const eccReal = 0.0167;
const rRealAU = (1 - eccReal * eccReal) / (1 + eccReal * Math.cos(theta));
document.getElementById('stat').textContent =
'真实日地距离: ' + rRealAU.toFixed(3) + ' AU(近日点0.983 / 远日点1.017) · ' +
(phase < 0.25 ? '接近近日点(快)' : phase > 0.75 ? '接近远日点(慢)' : '中途');
// 推进
if (!paused) {
const steps = Math.max(1, Math.round(speedMul * 2));
for (let i = 0; i < steps; i++) { theta = nextTheta(theta); }
moonAng += 0.045 * speedMul;
}
requestAnimationFrame(draw);
}
document.getElementById('btnPause').onclick = function () {
paused = !paused;
this.textContent = paused ? '继续' : '暂停';
};
document.getElementById('speed').oninput = function () {
speedMul = parseFloat(this.value);
};
resize();
requestAnimationFrame(draw);
</script>
</body>
</html>