-
视角距离:
-
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" },