From c4ea918fac2ca72b22e2de0f74d3e16f37838d44 Mon Sep 17 00:00:00 2001 From: rayd1o Date: Thu, 9 Apr 2026 02:12:22 +0800 Subject: [PATCH] fix: refine earth hud structure and bun startup flow - refactor the /earth HUD into class-first CSS layers with dedicated base, hud, and toolbar responsibilities - clean up Earth markup and runtime DOM hooks so shared panel, toolbar, tooltip, and status classes stay consistent after dynamic updates - keep HUD scaling configurable through extracted constants and remove leftover legacy panel/toolbar styling paths - make planet.sh self-bootstrap Bun and uv by prepending local runtime bins to PATH and auto-installing missing tools in fresh environments - document Bun as the frontend package manager of record and sync repository version metadata to 0.24.1 --- README.md | 18 + VERSION | 2 +- docs/CHANGELOG.md | 31 + docs/version-history.md | 4 +- frontend/package.json | 3 +- frontend/public/earth/css/base.css | 834 ++---------------- .../public/earth/css/coordinates-display.css | 43 +- frontend/public/earth/css/earth-stats.css | 54 +- frontend/public/earth/css/hud.css | 170 ++++ frontend/public/earth/css/info-panel.css | 196 +--- frontend/public/earth/css/legend.css | 65 +- frontend/public/earth/css/toolbar.css | 498 +++++++++++ frontend/public/earth/index.html | 194 ++-- frontend/public/earth/js/constants.js | 7 + frontend/public/earth/js/controls.js | 6 +- frontend/public/earth/js/legend.js | 2 +- frontend/public/earth/js/main.js | 118 ++- frontend/public/earth/js/ui.js | 90 +- planet.sh | 90 +- project_context.md | 3 + pyproject.toml | 2 +- rules.md | 3 + scripts/bootstrap-dev.sh | 5 +- uv.lock | 2 +- 24 files changed, 1248 insertions(+), 1192 deletions(-) create mode 100644 frontend/public/earth/css/hud.css create mode 100644 frontend/public/earth/css/toolbar.css diff --git a/README.md b/README.md index 3fe1507d..303a67d9 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,13 @@ | Axios | HTTP 客户端 | | Socket.io-client | WebSocket 客户端 | | ECharts | 统计图表 | +| Bun | 前端包管理与脚本运行 | + +前端工程统一使用 Bun: + +- 安装依赖使用 `bun install` +- 运行脚本使用 `bun run + + - - + -
-
-

智能星球计划

+
+
+

智能星球计划

现实层宇宙全息感知系统 卫星 · 海底光缆 · 算力基础设施
- -
-
-
- - -
- -
- - - - -
- -
- -
- - 100%重置缩放到100% - +
+ + 100%重置缩放到100% +
- -
-
-

坐标信息

-
- 经度: - 0.00° +
+

坐标信息

+
+ 经度: + 0.00°
-
- 纬度: - 0.00° +
+ 纬度: + 0.00°
-
缩放: 1.0x
-
鼠标位置: 无
+
缩放: 1.0x
+
鼠标位置: 无
-
-

线缆图例

+
+

线缆图例

-
+
Americas II
-
+
AU Aleutian A
-
+
AU Aleutian B
-
+
其他电缆
-
-

地球信息

-
- 电缆系统: - 0个 +
+

地球信息

+
+ 电缆系统: + 0个
-
- 状态: - - +
+ 状态: + -
-
- 登陆点: - 0个 +
+ 登陆点: + 0个
-
- 地形: - 开启 +
+ 地形: + 开启
-
- 卫星: - 0 颗 +
+ 卫星: + 0 颗
-
- BGP事件: - 0 条 +
+ BGP事件: + 0 条
-
- 观测站: - 0 个 +
+ 观测站: + 0 个
-
- BGP态势: - 暂无观测数据 +
+ BGP态势: + 暂无观测数据
-
- 视角距离: - 300 km +
+ 视角距离: + 300 km
-
- 纹理质量: - 8K 卫星图 +
+ 纹理质量: + 8K 卫星图
-
-
-
正在初始化全球态势数据...
-
同步卫星、海底光缆、登陆点与BGP态势数据
+
+
+
正在初始化全球态势数据...
+
同步卫星、海底光缆、登陆点与BGP态势数据
- -
+
+
diff --git a/frontend/public/earth/js/constants.js b/frontend/public/earth/js/constants.js index 05008c6b..e7a10b6b 100644 --- a/frontend/public/earth/js/constants.js +++ b/frontend/public/earth/js/constants.js @@ -12,6 +12,13 @@ export const CONFIG = { dragRotationScaleMax: 2.0, }; +export const HUD_CONFIG = { + scaleReferenceWidth: 1920, + scaleReferenceHeight: 1080, + minScale: 0.7, + maxScale: 1, +}; + // Earth coordinate constants export const EARTH_CONFIG = { tilt: 23.5, // earth tilt angle (degrees) diff --git a/frontend/public/earth/js/controls.js b/frontend/public/earth/js/controls.js index ea07bf13..22b58736 100644 --- a/frontend/public/earth/js/controls.js +++ b/frontend/public/earth/js/controls.js @@ -76,7 +76,7 @@ function setFloatingMenuOpen(group, shouldOpen) { } function setButtonTooltip(button, text) { - const tooltip = button?.querySelector(".tooltip"); + const tooltip = button?.querySelector(".earth-toolbar-tooltip"); if (tooltip) { tooltip.textContent = text; } @@ -566,7 +566,7 @@ function updateRotateUI() { if (btn) { btn.classList.toggle("active", autoRotate); btn.classList.toggle("is-stopped", !autoRotate); - const tooltip = btn.querySelector(".tooltip"); + const tooltip = btn.querySelector(".earth-toolbar-tooltip"); if (tooltip) tooltip.textContent = autoRotate ? "暂停旋转" : "开始旋转"; } } @@ -599,7 +599,7 @@ function updateLayoutUI(container) { const btn = document.getElementById("layout-toggle"); if (btn) { btn.classList.toggle("active", layoutExpanded); - const tooltip = btn.querySelector(".tooltip"); + const tooltip = btn.querySelector(".earth-toolbar-tooltip"); const nextLabel = layoutExpanded ? "恢复布局" : "最大化布局"; btn.title = nextLabel; if (tooltip) tooltip.textContent = nextLabel; diff --git a/frontend/public/earth/js/legend.js b/frontend/public/earth/js/legend.js index 94e44535..45f9fc35 100644 --- a/frontend/public/earth/js/legend.js +++ b/frontend/public/earth/js/legend.js @@ -61,7 +61,7 @@ function renderLegend(mode) { .join(""); legend.innerHTML = ` -

${config.title}

+

${config.title}

${itemsHtml}
`; } diff --git a/frontend/public/earth/js/main.js b/frontend/public/earth/js/main.js index 653be064..d757be95 100644 --- a/frontend/public/earth/js/main.js +++ b/frontend/public/earth/js/main.js @@ -1,7 +1,7 @@ import * as THREE from "three"; import { createNoise3D } from "simplex-noise"; -import { CONFIG, CABLE_CONFIG, CABLE_STATE } from "./constants.js"; +import { CONFIG, HUD_CONFIG, CABLE_CONFIG, CABLE_STATE } from "./constants.js"; import { vector3ToLatLon, screenToEarthCoords } from "./utils.js"; import { showStatusMessage, @@ -174,6 +174,7 @@ const cleanupFns = []; const DRAG_SMOOTHING_FACTOR = 0.18; const INERTIA_DAMPING = 0.92; const INERTIA_MIN_VELOCITY = 0.00008; +const ACTIVE_BGP_TOOLTIP_TEXT = "隐藏BGP观测"; const HUD_INTERACTIVE_SELECTORS = [ "#info-panel", "#info-panel *", @@ -195,6 +196,17 @@ function bindListener(target, eventName, handler, options) { ); } +function getViewportAspect() { + return window.innerWidth / window.innerHeight; +} + +function syncRendererViewport() { + if (!camera || !renderer) return; + camera.aspect = getViewportAspect(); + camera.updateProjectionMatrix(); + renderer.setSize(window.innerWidth, window.innerHeight); +} + function isEventOnHud(event) { const target = event?.target; if (!(target instanceof Element)) return false; @@ -520,6 +532,47 @@ function showBGPCollectorInfo(marker) { }); } +function getBGPStatusText(bgpResult) { + if (bgpResult.totalCount > 0) { + return `${bgpResult.totalCount} 起活跃事件`; + } + if (bgpResult.anomalyCount > 0) { + return `${bgpResult.anomalyCount} 条活跃异常`; + } + return "当前无活跃事件"; +} + +function updateBGPHud(bgpResult) { + const bgpBtn = document.getElementById("toggle-bgp"); + if (bgpBtn) { + bgpBtn.classList.add("active"); + const tooltip = bgpBtn.querySelector(".earth-toolbar-tooltip"); + if (tooltip) { + tooltip.textContent = ACTIVE_BGP_TOOLTIP_TEXT; + } + } + + const bgpCountEl = document.getElementById("bgp-anomaly-count"); + if (bgpCountEl) { + bgpCountEl.textContent = `${bgpResult.totalCount} 起`; + } + + const bgpCollectorEl = document.getElementById("bgp-collector-count"); + if (bgpCollectorEl) { + bgpCollectorEl.textContent = `${bgpResult.collectorCount} 个`; + } + + const bgpStatusEl = document.getElementById("bgp-status-summary"); + if (bgpStatusEl) { + bgpStatusEl.textContent = getBGPStatusText(bgpResult); + } +} + +function clearSelectionAndInfo() { + clearLockedObject(); + hideInfoCard(); +} + function getBGPRelatedCableNames(marker) { const items = Array.isArray(marker?.userData?.related_cables) ? marker.userData.related_cables @@ -655,7 +708,7 @@ function updateSatelliteToggleUi(enabled, satelliteCount = getSatelliteCount()) const satBtn = document.getElementById("toggle-satellites"); if (satBtn) { satBtn.classList.toggle("active", enabled); - const tooltip = satBtn.querySelector(".tooltip"); + const tooltip = satBtn.querySelector(".earth-toolbar-tooltip"); if (tooltip) tooltip.textContent = enabled ? "隐藏卫星" : "显示卫星"; } @@ -669,7 +722,7 @@ function updateCableToggleUi(enabled) { const cableBtn = document.getElementById("toggle-cables"); if (cableBtn) { cableBtn.classList.toggle("active", enabled); - const tooltip = cableBtn.querySelector(".tooltip"); + const tooltip = cableBtn.querySelector(".earth-toolbar-tooltip"); if (tooltip) tooltip.textContent = enabled ? "隐藏线缆" : "显示线缆"; } @@ -799,11 +852,12 @@ export function init() { destroyed = false; initialized = true; simplex = createNoise3D(); + updateHudScale(); scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera( 75, - window.innerWidth / window.innerHeight, + getViewportAspect(), 0.1, 1000, ); @@ -815,7 +869,7 @@ export function init() { alpha: false, powerPreference: "high-performance", }); - renderer.setSize(window.innerWidth, window.innerHeight); + syncRendererViewport(); renderer.setClearColor(0x0a0a1a, 1); renderer.setPixelRatio(window.devicePixelRatio); @@ -898,8 +952,7 @@ async function loadData(showWhiteSphere = false) { : "同步卫星、海底光缆、登陆点与BGP态势数据", ); setLoading(true); - clearLockedObject(); - hideInfoCard(); + clearSelectionAndInfo(); if (showWhiteSphere && earth.material) { earthTexture = earth.material.map; @@ -915,29 +968,7 @@ async function loadData(showWhiteSphere = false) { clearBGPData(earth); const bgpResult = await loadBGPAnomalies(scene, earth); toggleBGP(true); - const bgpBtn = document.getElementById("toggle-bgp"); - if (bgpBtn) { - bgpBtn.classList.add("active"); - const tooltip = bgpBtn.querySelector(".tooltip"); - if (tooltip) tooltip.textContent = "隐藏BGP观测"; - } - const bgpCountEl = document.getElementById("bgp-anomaly-count"); - if (bgpCountEl) { - bgpCountEl.textContent = `${bgpResult.totalCount} 起`; - } - const bgpCollectorEl = document.getElementById("bgp-collector-count"); - if (bgpCollectorEl) { - bgpCollectorEl.textContent = `${bgpResult.collectorCount} 个`; - } - const bgpStatusEl = document.getElementById("bgp-status-summary"); - if (bgpStatusEl) { - bgpStatusEl.textContent = - bgpResult.totalCount > 0 - ? `${bgpResult.totalCount} 起活跃事件` - : bgpResult.anomalyCount > 0 - ? `${bgpResult.anomalyCount} 条活跃异常` - : "当前无活跃事件"; - } + updateBGPHud(bgpResult); return bgpResult; })(), ]); @@ -997,8 +1028,7 @@ export async function setCablesEnabled(enabled) { } if (!enabled) { - clearLockedObject(); - hideInfoCard(); + clearSelectionAndInfo(); disableCables(); showStatusMessage("线缆已隐藏", "info"); return 0; @@ -1032,8 +1062,7 @@ export async function setSatellitesEnabled(enabled) { } if (!enabled) { - clearLockedObject(); - hideInfoCard(); + clearSelectionAndInfo(); disableSatellites(); return 0; } @@ -1078,11 +1107,24 @@ function setupEventListeners() { bindListener(renderer.domElement, "click", handleClick); } +function updateHudScale() { + const widthScale = window.innerWidth / HUD_CONFIG.scaleReferenceWidth; + const heightScale = window.innerHeight / HUD_CONFIG.scaleReferenceHeight; + const nextScale = THREE.MathUtils.clamp( + Math.min(widthScale, heightScale), + HUD_CONFIG.minScale, + HUD_CONFIG.maxScale, + ); + + document.documentElement.style.setProperty( + "--hud-scale", + nextScale.toFixed(3), + ); +} + function onWindowResize() { - if (!camera || !renderer) return; - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - renderer.setSize(window.innerWidth, window.innerHeight); + updateHudScale(); + syncRendererViewport(); } function getFrontFacingCables(cableLines) { diff --git a/frontend/public/earth/js/ui.js b/frontend/public/earth/js/ui.js index d472a18f..480a4117 100644 --- a/frontend/public/earth/js/ui.js +++ b/frontend/public/earth/js/ui.js @@ -3,12 +3,18 @@ let statusTimeoutId = null; let statusHideTimeoutId = null; let statusReplayTimeoutId = null; +const STATUS_BASE_CLASS = "earth-status-message"; -// Show status message -export function showStatusMessage(message, type = "info") { - const statusEl = document.getElementById("status-message"); - if (!statusEl) return; +function getElement(id) { + return document.getElementById(id); +} +function setElementDisplay(element, visible, displayValue = "block") { + if (!element) return; + element.style.display = visible ? displayValue : "none"; +} + +function clearStatusTimers() { if (statusTimeoutId) { clearTimeout(statusTimeoutId); statusTimeoutId = null; @@ -23,18 +29,26 @@ export function showStatusMessage(message, type = "info") { clearTimeout(statusReplayTimeoutId); statusReplayTimeoutId = null; } +} + +// Show status message +export function showStatusMessage(message, type = "info") { + const statusEl = getElement("status-message"); + if (!statusEl) return; + + clearStatusTimers(); const startShow = () => { statusEl.textContent = message; - statusEl.className = `status-message ${type}`; - statusEl.style.display = "block"; + statusEl.className = `${STATUS_BASE_CLASS} ${type}`; + setElementDisplay(statusEl, true); statusEl.offsetHeight; statusEl.classList.add("visible"); statusTimeoutId = setTimeout(() => { statusEl.classList.remove("visible"); statusHideTimeoutId = setTimeout(() => { - statusEl.style.display = "none"; + setElementDisplay(statusEl, false); statusEl.textContent = ""; statusHideTimeoutId = null; }, 280); @@ -56,9 +70,9 @@ export function showStatusMessage(message, type = "info") { // Update coordinates display export function updateCoordinatesDisplay(lat, lon, alt = 0) { - const longitudeEl = document.getElementById("longitude-value"); - const latitudeEl = document.getElementById("latitude-value"); - const mouseCoordsEl = document.getElementById("mouse-coords"); + const longitudeEl = getElement("longitude-value"); + const latitudeEl = getElement("latitude-value"); + const mouseCoordsEl = getElement("mouse-coords"); if (longitudeEl) longitudeEl.textContent = lon.toFixed(2) + "°"; if (latitudeEl) latitudeEl.textContent = lat.toFixed(2) + "°"; @@ -70,10 +84,10 @@ export function updateCoordinatesDisplay(lat, lon, alt = 0) { // Update zoom display export function updateZoomDisplay(zoomLevel, distance) { const percent = Math.round(zoomLevel * 100); - const zoomValueEl = document.getElementById("zoom-value"); - const zoomLevelEl = document.getElementById("zoom-level"); - const slider = document.getElementById("zoom-slider"); - const cameraDistanceEl = document.getElementById("camera-distance"); + const zoomValueEl = getElement("zoom-value"); + const zoomLevelEl = getElement("zoom-level"); + const slider = getElement("zoom-slider"); + const cameraDistanceEl = getElement("camera-distance"); if (zoomValueEl) zoomValueEl.textContent = percent + "%"; if (zoomLevelEl) zoomLevelEl.textContent = "缩放: " + percent + "%"; @@ -83,13 +97,13 @@ export function updateZoomDisplay(zoomLevel, distance) { // Update earth stats export function updateEarthStats(stats) { - const cableCountEl = document.getElementById("cable-count"); - const landingPointCountEl = document.getElementById("landing-point-count"); - const bgpAnomalyCountEl = document.getElementById("bgp-anomaly-count"); - const bgpCollectorCountEl = document.getElementById("bgp-collector-count"); - const bgpStatusSummaryEl = document.getElementById("bgp-status-summary"); - const terrainStatusEl = document.getElementById("terrain-status"); - const textureQualityEl = document.getElementById("texture-quality"); + const cableCountEl = getElement("cable-count"); + const landingPointCountEl = getElement("landing-point-count"); + const bgpAnomalyCountEl = getElement("bgp-anomaly-count"); + const bgpCollectorCountEl = getElement("bgp-collector-count"); + const bgpStatusSummaryEl = getElement("bgp-status-summary"); + const terrainStatusEl = getElement("terrain-status"); + const textureQualityEl = getElement("texture-quality"); if (cableCountEl) cableCountEl.textContent = stats.cableCount || 0; if (landingPointCountEl) @@ -108,14 +122,14 @@ export function updateEarthStats(stats) { // Show/hide loading export function setLoading(loading) { - const loadingEl = document.getElementById("loading"); + const loadingEl = getElement("loading"); if (!loadingEl) return; - loadingEl.style.display = loading ? "block" : "none"; + setElementDisplay(loadingEl, loading); } export function setLoadingMessage(title, subtitle = "") { - const titleEl = document.getElementById("loading-title"); - const subtitleEl = document.getElementById("loading-subtitle"); + const titleEl = getElement("loading-title"); + const subtitleEl = getElement("loading-subtitle"); if (titleEl) { titleEl.textContent = title; @@ -128,48 +142,46 @@ export function setLoadingMessage(title, subtitle = "") { // Show tooltip export function showTooltip(x, y, content) { - const tooltip = document.getElementById("tooltip"); + const tooltip = getElement("tooltip"); if (!tooltip) return; tooltip.innerHTML = content; tooltip.style.left = x + "px"; tooltip.style.top = y + "px"; - tooltip.style.display = "block"; + setElementDisplay(tooltip, true); } // Hide tooltip export function hideTooltip() { - const tooltip = document.getElementById("tooltip"); + const tooltip = getElement("tooltip"); if (tooltip) { - tooltip.style.display = "none"; + setElementDisplay(tooltip, false); } } // Show error message export function showError(message) { - const errorEl = document.getElementById("error-message"); + const errorEl = getElement("error-message"); if (!errorEl) return; errorEl.textContent = message; - errorEl.style.display = "block"; + setElementDisplay(errorEl, true); } // Hide error message export function hideError() { - const errorEl = document.getElementById("error-message"); + const errorEl = getElement("error-message"); if (errorEl) { - errorEl.style.display = "none"; + setElementDisplay(errorEl, false); errorEl.textContent = ""; } } export function clearUiState() { - if (statusTimeoutId) { - clearTimeout(statusTimeoutId); - statusTimeoutId = null; - } + clearStatusTimers(); - const statusEl = document.getElementById("status-message"); + const statusEl = getElement("status-message"); if (statusEl) { - statusEl.style.display = "none"; + statusEl.className = STATUS_BASE_CLASS; + setElementDisplay(statusEl, false); statusEl.textContent = ""; } diff --git a/planet.sh b/planet.sh index 7f7df5e8..a3ab5e6f 100755 --- a/planet.sh +++ b/planet.sh @@ -60,6 +60,24 @@ FRONTEND_RUNTIME_BIN="${FRONTEND_RUNTIME_BIN:-}" FRONTEND_RUNTIME_SOURCE="${FRONTEND_RUNTIME_SOURCE:-}" FRONTEND_PID_FILE="/tmp/planet_frontend.pid" +prepend_path_once() { + local path_entry="$1" + [ -n "$path_entry" ] || return 0 + [ -d "$path_entry" ] || return 0 + + case ":$PATH:" in + *":$path_entry:"*) ;; + *) export PATH="$path_entry:$PATH" ;; + esac +} + +ensure_local_runtime_bins_on_path() { + prepend_path_once "$HOME/.local/bin" + prepend_path_once "$HOME/.bun/bin" +} + +ensure_local_runtime_bins_on_path + # Shell / Docker helpers compose_up() { if docker compose version >/dev/null 2>&1; then @@ -320,12 +338,62 @@ run_with_retry() { return 1 } +install_uv_if_needed() { + if command -v uv >/dev/null 2>&1; then + return 0 + fi + + set_wait_detail "未找到 uv,准备自动安装" + mkdir -p "$HOME/.local/bin" + + if ! run_with_retry \ + "$DEPENDENCY_INSTALL_MAX_RETRIES" \ + "$DEPENDENCY_INSTALL_RETRY_INTERVAL" \ + "uv 安装失败,已重试 ${DEPENDENCY_INSTALL_MAX_RETRIES} 次" \ + "安装 uv" \ + sh -c 'curl -LsSf https://astral.sh/uv/install.sh | sh'; then + exit 1 + fi + + ensure_local_runtime_bins_on_path + + if ! command -v uv >/dev/null 2>&1; then + log_error "uv 安装完成后仍未找到命令,请检查 ~/.local/bin" + exit 1 + fi +} + +install_bun_if_needed() { + if command -v bun >/dev/null 2>&1 || [ -x "$HOME/.bun/bin/bun" ]; then + ensure_local_runtime_bins_on_path + return 0 + fi + + set_wait_detail "未找到 bun,准备自动安装" + mkdir -p "$HOME/.bun" + + if ! run_with_retry \ + "$DEPENDENCY_INSTALL_MAX_RETRIES" \ + "$DEPENDENCY_INSTALL_RETRY_INTERVAL" \ + "bun 安装失败,已重试 ${DEPENDENCY_INSTALL_MAX_RETRIES} 次" \ + "安装 bun" \ + sh -c 'curl -fsSL https://bun.sh/install | bash'; then + exit 1 + fi + + ensure_local_runtime_bins_on_path + + if [ ! -x "$HOME/.bun/bin/bun" ] && ! command -v bun >/dev/null 2>&1; then + log_error "bun 安装完成后仍未找到命令,请检查 ~/.bun/bin" + exit 1 + fi +} + ensure_uv_backend_deps() { set_wait_detail "检查后端 uv 环境" if ! command -v uv >/dev/null 2>&1; then - log_error "未找到 uv,请先安装 uv 并加入 PATH" - exit 1 + install_uv_if_needed fi cd "$SCRIPT_DIR" @@ -357,6 +425,15 @@ resolve_frontend_runtime() { return 0 fi + ensure_local_runtime_bins_on_path + + if [ -x "$HOME/.bun/bin/bun" ]; then + FRONTEND_RUNTIME_BIN="$HOME/.bun/bin/bun" + FRONTEND_RUNTIME_SOURCE="local-install" + WAIT_SPINNER_DETAIL="已找到 ~/.bun/bin/bun" + return 0 + fi + set_wait_detail "读取 zsh 配置中的 bun" if resolve_bun_from_shell_configs "zsh" "$HOME/.zshrc" "$HOME/.zprofile"; then WAIT_SPINNER_DETAIL="已找到 zsh 配置中的 bun" @@ -413,9 +490,12 @@ ensure_frontend_deps() { set_wait_detail "解析前端运行时" if ! resolve_frontend_runtime; then - close_wait_session_context "$owns_wait_session" - log_error "未找到 bun,已按 zsh > powershell 顺序检查启动配置" - exit 1 + install_bun_if_needed + if ! resolve_frontend_runtime; then + close_wait_session_context "$owns_wait_session" + log_error "未找到 bun,自动安装后仍无法解析运行时" + exit 1 + fi fi cd "$SCRIPT_DIR/frontend" diff --git a/project_context.md b/project_context.md index ce1d4f98..df8cf6de 100644 --- a/project_context.md +++ b/project_context.md @@ -40,6 +40,7 @@ - **State Management:** React Query - **Real-time:** Socket.io-client - **Charts:** ECharts +- **Package Manager / Runner:** Bun 1 ### Visualization (UE5) - **Engine:** Unreal Engine 5.3+ @@ -210,6 +211,8 @@ bun run build bun run preview ``` +前端统一使用 Bun,不使用 `npm`、`pnpm`、`yarn`。 + ### Docker ```bash docker-compose up -d diff --git a/pyproject.toml b/pyproject.toml index 32deb601..6141523c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "planet" -version = "0.24.0" +version = "0.24.1" description = "智能星球计划 - 态势感知系统" requires-python = ">=3.14" dependencies = [ diff --git a/rules.md b/rules.md index 99705a68..9f16df9a 100644 --- a/rules.md +++ b/rules.md @@ -157,6 +157,9 @@ git fetch origin && git rebase origin/main - Prefer well-maintained, widely-used libraries - Pin dependency versions in `pyproject.toml`, `uv.lock`, and `package.json` - Review security advisories with `pip-audit` and `bun audit` +- Frontend package management and script execution use **Bun only** +- Frontend commands must use `bun install` / `bun run ...` +- **NEVER** use `npm` / `pnpm` / `yarn` for the frontend project - **NEVER** add unknown packages --- diff --git a/scripts/bootstrap-dev.sh b/scripts/bootstrap-dev.sh index 3f0da561..d0fabe68 100755 --- a/scripts/bootstrap-dev.sh +++ b/scripts/bootstrap-dev.sh @@ -106,7 +106,10 @@ print_next_steps() { echo "下一步建议:" echo " 1. cd \"$ROOT_DIR\"" echo " 2. uv run pytest backend/tests/test_api.py -q -s" - echo " 3. ./planet.sh start" + echo " 3. cd \"$ROOT_DIR/frontend\" && bun run build" + echo " 4. cd \"$ROOT_DIR\" && ./planet.sh start" + echo "" + echo "前端统一使用 Bun,不使用 npm/pnpm/yarn。" } main() { diff --git a/uv.lock b/uv.lock index 7ef15c78..da25f7ce 100644 --- a/uv.lock +++ b/uv.lock @@ -475,7 +475,7 @@ wheels = [ [[package]] name = "planet" -version = "0.24.0" +version = "0.24.1" source = { virtual = "." } dependencies = [ { name = "aiofiles" },