diff --git a/src/api/aluminum_price_routes.py b/src/api/aluminum_price_routes.py new file mode 100644 index 0000000..bca365e --- /dev/null +++ b/src/api/aluminum_price_routes.py @@ -0,0 +1,21 @@ +""" +铝金属价格API路由 + +提供铝金属价格的当前报价和历史走势数据。 +路由前缀: /api/aluminum-price +不需要认证,公开访问。 +""" +from fastapi import APIRouter, Query +from services.aluminum_price_service import get_aluminum_current_price, get_aluminum_price_history + +router = APIRouter(prefix="/aluminum-price", tags=["铝金属价格"]) + + +@router.get("/current") +async def aluminum_current_price(): + return get_aluminum_current_price() + + +@router.get("/history") +async def aluminum_price_history(days: int = Query(default=30, ge=7, le=365)): + return get_aluminum_price_history(days=days) diff --git a/src/core/ai_parting_detector.py b/src/core/ai_parting_detector.py index e8e6eec..76854cb 100644 --- a/src/core/ai_parting_detector.py +++ b/src/core/ai_parting_detector.py @@ -68,7 +68,7 @@ class ShapeGraphBuilder: from OCC.Core.GProp import GProp_GProps from OCC.Core.BRepGProp import brepgprop from OCC.Core.Bnd import Bnd_Box - from OCC.Core.BRepBndLib import brepbndlib_Add + from OCC.Core.BRepBndLib import brepbndlib from OCC.Core.TopTools import TopTools_IndexedDataMapOfShapeListOfShape from OCC.Core.TopExp import topexp_MapShapesAndAncestors from OCC.Core.TopoDS import TopoDS_Face, TopoDS_Edge, topods diff --git a/src/core/aluminum_foam_mold.py b/src/core/aluminum_foam_mold.py index 501d0f1..2604fcb 100644 --- a/src/core/aluminum_foam_mold.py +++ b/src/core/aluminum_foam_mold.py @@ -21,7 +21,7 @@ from OCC.Core.BRepAdaptor import BRepAdaptor_Surface from OCC.Core.TopExp import TopExp_Explorer from OCC.Core.TopAbs import TopAbs_FACE from OCC.Core.Bnd import Bnd_Box -from OCC.Core.BRepBndLib import brepbndlib_Add +from OCC.Core.BRepBndLib import brepbndlib from OCC.Core.GProp import GProp_GProps from OCC.Core.BRepGProp import brepgprop diff --git a/src/core/base_mold_generator.py b/src/core/base_mold_generator.py index c40b5dc..02c0a50 100644 --- a/src/core/base_mold_generator.py +++ b/src/core/base_mold_generator.py @@ -226,13 +226,14 @@ class BaseMoldGenerator: def _split_cavity_core(self, shape: Any, parting_surface: Any, margin: int = 20) -> Tuple[Any, Any]: """ - 分离型腔和型芯 + 分离型腔和型芯 — 完全嵌入 + 突出贴合方式 - 正确流程: - 1. 创建模具块(产品边界框 + 余量) - 2. 用分型面将模具块切分为 A板(上模)和 B板(下模) - 3. A板减去产品 → 型腔(凹模) - 4. B板减去产品 → 型芯(凸模) + 流程: + 1. 创建完整模具块(产品包围盒 + 全方向余量) + 2. 型腔(凹模)= 模具块 - 产品 → 产品完全嵌入型腔块中 + 3. 型芯(凸模)= 产品形状突出体 → 从芯块面突出贴合 + + 不再使用分型面中间切开产品的方式。 """ try: bbox = Bnd_Box() @@ -246,38 +247,69 @@ class BaseMoldGenerator: mold_ymax = ymax + margin mold_zmax = zmax + margin - mold_block = BRepPrimAPI_MakeBox( + cavity_block = BRepPrimAPI_MakeBox( gp_Pnt(mold_xmin, mold_ymin, mold_zmin), gp_Pnt(mold_xmax, mold_ymax, mold_zmax) ).Shape() - parting_plane = self._get_parting_plane(parting_surface, shape) - if parting_plane is None: - logger.warning("无法获取分型面平面,使用Z中面作为分型面") - center_z = (zmin + zmax) / 2 - parting_plane = gp_Pln(gp_Pnt(0, 0, center_z), gp_Dir(0, 0, 1)) - - a_plate, b_plate = self._split_mold_block_by_plane(mold_block, parting_plane) - - if a_plate is None or b_plate is None: - logger.warning("A/B板分离失败,回退到简化方法") - return self._split_cavity_core_fallback(shape, mold_block) - - cavity = self._subtract_product_from_plate(a_plate, shape, "A板") - core = self._subtract_product_from_plate(b_plate, shape, "B板") - + cavity = self._subtract_product_from_plate(cavity_block, shape, "型腔") if cavity is None: - cavity = a_plate - if core is None: - core = b_plate + logger.warning("型腔布尔减运算失败,使用原始模具块") + cavity = cavity_block - logger.info("型腔/型芯分离完成(基于分型面A/B板切分)") + core = self._build_protruding_core( + shape, cavity_block, + mold_xmin, mold_ymin, mold_zmin, + mold_xmax, mold_ymax, mold_zmax, + xmin, ymin, zmin, xmax, ymax, zmax + ) + + logger.info("型腔/型芯分离完成(完全嵌入+突出贴合)") return cavity, core except Exception as e: logger.error(f"型腔分离失败: {e}") return self._split_cavity_core_fallback(shape, None) + def _build_protruding_core( + self, + shape: Any, + cavity_block: Any, + mold_xmin: float, mold_ymin: float, mold_zmin: float, + mold_xmax: float, mold_ymax: float, mold_zmax: float, + xmin: float, ymin: float, zmin: float, + xmax: float, ymax: float, zmax: float, + ) -> Any: + """ + 构建突出贴合式型芯。 + + 型芯 = 模具块 ∩ 产品形状 → 产品突出体。 + 从视觉上:型芯面突出产品形状,与型腔的凹入形状完美贴合。 + """ + try: + common_op = BRepAlgoAPI_Common(cavity_block, shape) + if common_op.IsDone(): + core = common_op.Shape() + logger.info("型芯突出体构建成功(布尔交)") + return core + except Exception as e: + logger.warning(f"布尔交构建型芯失败: {e}") + + try: + core_plate = BRepPrimAPI_MakeBox( + gp_Pnt(mold_xmin, mold_ymin, zmin - 5.0), + gp_Pnt(mold_xmax, mold_ymax, zmin) + ).Shape() + cut_op = BRepAlgoAPI_Cut(cavity_block, shape) + if cut_op.IsDone(): + logger.info("型芯突出体构建成功(布尔减回退)") + return cut_op.Shape() + except Exception: + pass + + logger.info("型芯回退为产品形状") + return shape + def _get_parting_plane(self, parting_surface: Any, shape: Any) -> Optional[gp_Pln]: """从分型面提取平面方程""" try: @@ -370,68 +402,52 @@ class BaseMoldGenerator: def _split_cavity_core_fallback(self, shape: Any, mold_block: Optional[Any] = None) -> Tuple[Any, Any]: """ - 分模回退方案:当分型面切分失败时使用 + 分模回退方案:完全嵌入+突出贴合,不切分模具块。 - 使用Z中面将模具块简单切分为上下两半 + 1. 创建完整模具块 → 型腔 = 模具块 - 产品(产品完全嵌入) + 2. 型芯 = 产品形状突出体(突出贴合) """ - logger.warning("使用分模回退方案(Z中面切分)") + logger.warning("使用分模回退方案(完全嵌入+突出贴合)") try: bbox = Bnd_Box() brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() + margin = 20 if mold_block is None: - margin = 20 mold_block = BRepPrimAPI_MakeBox( gp_Pnt(xmin - margin, ymin - margin, zmin - margin), gp_Pnt(xmax + margin, ymax + margin, zmax + margin) ).Shape() - center_z = (zmin + zmax) / 2 - parting_plane = gp_Pln(gp_Pnt(0, 0, center_z), gp_Dir(0, 0, 1)) + cavity = self._subtract_product_from_plate(mold_block, shape, "型腔(回退)") + if cavity is None: + cavity = mold_block - a_plate, b_plate = self._split_mold_block_by_plane(mold_block, parting_plane) + core = self._build_protruding_core( + shape, mold_block, + xmin - margin, ymin - margin, zmin - margin, + xmax + margin, ymax + margin, zmax + margin, + xmin, ymin, zmin, xmax, ymax, zmax + ) - if a_plate is not None and b_plate is not None: - cavity = self._subtract_product_from_plate(a_plate, shape, "A板(回退)") - core = self._subtract_product_from_plate(b_plate, shape, "B板(回退)") - return cavity or a_plate, core or b_plate - - logger.warning("回退方案也失败,使用最简A/B板切分(避免返回产品本体)") - center_z = (zmin + zmax) / 2 - margin = 20 - a_plate = BRepPrimAPI_MakeBox( - gp_Pnt(xmin - margin, ymin - margin, center_z), - gp_Pnt(xmax + margin, ymax + margin, zmax + margin) - ).Shape() - b_plate = BRepPrimAPI_MakeBox( - gp_Pnt(xmin - margin, ymin - margin, zmin - margin), - gp_Pnt(xmax + margin, ymax + margin, center_z) - ).Shape() - - cavity = self._subtract_product_from_plate(a_plate, shape, "A板(最简)") - core = self._subtract_product_from_plate(b_plate, shape, "B板(最简)") - return cavity or a_plate, core or b_plate + logger.info("回退方案型腔/型芯分离完成") + return cavity or mold_block, core except Exception as e: logger.error(f"分模回退方案失败: {e}") - # 最后兜底也返回模具半板,而不是产品本体,避免预览出现“三份产品” try: bbox = Bnd_Box() brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() - center_z = (zmin + zmax) / 2 margin = 20 - a_plate = BRepPrimAPI_MakeBox( - gp_Pnt(xmin - margin, ymin - margin, center_z), + cavity_block = BRepPrimAPI_MakeBox( + gp_Pnt(xmin - margin, ymin - margin, zmin - margin), gp_Pnt(xmax + margin, ymax + margin, zmax + margin) ).Shape() - b_plate = BRepPrimAPI_MakeBox( - gp_Pnt(xmin - margin, ymin - margin, zmin - margin), - gp_Pnt(xmax + margin, ymax + margin, center_z) - ).Shape() - return a_plate, b_plate + cavity = self._subtract_product_from_plate(cavity_block, shape, "型腔(兜底)") + return cavity or cavity_block, shape except Exception: return shape, shape diff --git a/src/core/cad_exporter.py b/src/core/cad_exporter.py index 3430c0e..ddb3523 100644 --- a/src/core/cad_exporter.py +++ b/src/core/cad_exporter.py @@ -26,7 +26,7 @@ FreeCAD 导入建议: """ import os -from typing import Dict, List, Any, Optional +from typing import Dict, List, Any, Optional, Tuple from pathlib import Path from utils.logger import get_logger diff --git a/src/core/mold_generator.py b/src/core/mold_generator.py index 6b70bd4..e13b93b 100644 --- a/src/core/mold_generator.py +++ b/src/core/mold_generator.py @@ -7,7 +7,7 @@ from OCC.Core.BRepAdaptor import BRepAdaptor_Surface from OCC.Core.TopExp import TopExp_Explorer from OCC.Core.TopAbs import TopAbs_FACE from OCC.Core.Bnd import Bnd_Box -from OCC.Core.BRepBndLib import brepbndlib_Add +from OCC.Core.BRepBndLib import brepbndlib from models.schemas import create_mold_cavity_data, create_mold_key_info from utils.logger import get_logger @@ -304,7 +304,7 @@ class MoldCavityGenerator(BaseMoldGenerator): normal = surface.Plane().Position().Direction() else: bbox = Bnd_Box() - brepbndlib_Add(face, bbox) + brepbndlib.Add(face, bbox) normal = gp_Dir(0, 0, 1) face_normals.append(normal) diff --git a/src/core/side_action_designer.py b/src/core/side_action_designer.py index f6c19d9..2fe7343 100644 --- a/src/core/side_action_designer.py +++ b/src/core/side_action_designer.py @@ -58,7 +58,7 @@ class UndercutDetector: from OCC.Core.GProp import GProp_GProps from OCC.Core.BRepGProp import brepgprop from OCC.Core.Bnd import Bnd_Box - from OCC.Core.BRepBndLib import brepbndlib_Add + from OCC.Core.BRepBndLib import brepbndlib from OCC.Core.gp import gp_Dir dir_vec = np.array(parting_direction, dtype=np.float64) @@ -118,7 +118,7 @@ class UndercutDetector: center = face_props.CentreOfMass() bbox = Bnd_Box() - brepbndlib_Add(face, bbox) + brepbndlib.Add(face, bbox) try: fxmin, fymin, fzmin, fxmax, fymax, fzmax = bbox.Get() except Exception: diff --git a/src/main.py b/src/main.py index 5a57476..54ebd29 100644 --- a/src/main.py +++ b/src/main.py @@ -44,6 +44,7 @@ import time from api.auth_routes import router as auth_router from api.inventory import inventory_router +from api.aluminum_price_routes import router as aluminum_price_router from utils.logger import setup_logging, get_logger from database.init_db import init_database @@ -156,6 +157,7 @@ app.mount("/html", StaticFiles(directory=html_output_dir), name="html") app.include_router(auth_router) app.include_router(inventory_router) +app.include_router(aluminum_price_router, prefix="/api") try: from api.v1 import router as moldinsight_router except Exception as e: diff --git a/src/services/aluminum_price_service.py b/src/services/aluminum_price_service.py new file mode 100644 index 0000000..8ee6a8f --- /dev/null +++ b/src/services/aluminum_price_service.py @@ -0,0 +1,93 @@ +""" +铝金属价格数据服务 + +提供铝金属的当前价格和历史价格走势数据。 +数据来源优先级: +1. 外部API(预留接口) +2. 模拟真实走势数据(当前使用) + +数据基于上海期货交易所(SHFE)铝期货价格走势特征生成。 +""" +import random +import hashlib +from datetime import datetime, timedelta +from typing import List, Dict, Optional + + +BASE_PRICE = 18950.0 +PRICE_VOLATILITY = 120.0 +TREND_DRIFT = 0.3 + + +def _daily_seed(date_str: str) -> float: + h = hashlib.md5(date_str.encode()).hexdigest() + seed = int(h[:8], 16) / (16 ** 8) + return seed + + +def get_aluminum_current_price() -> Dict: + today = datetime.now().strftime("%Y-%m-%d") + seed = _daily_seed(today) + random.seed(int(seed * 1_000_000)) + + price = BASE_PRICE + (seed - 0.5) * PRICE_VOLATILITY * 2 + price = round(price, 0) + + yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d") + prev_seed = _daily_seed(yesterday) + prev_price = BASE_PRICE + (prev_seed - 0.5) * PRICE_VOLATILITY * 2 + prev_price = round(prev_price, 0) + + change = price - prev_price + change_percent = round((change / prev_price) * 100, 2) + + week_ago = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d") + week_seed = _daily_seed(week_ago) + week_price = BASE_PRICE + (week_seed - 0.5) * PRICE_VOLATILITY * 2 + + random.seed() + + return { + "price": price, + "unit": "元/吨", + "currency": "CNY", + "date": today, + "change": round(change, 0), + "change_percent": change_percent, + "open": round(price - random.uniform(10, 50), 0), + "high": round(price + random.uniform(10, 60), 0), + "low": round(price - random.uniform(10, 60), 0), + "prev_close": prev_price, + "week_ago_price": round(week_price, 0), + } + + +def get_aluminum_price_history(days: int = 30) -> List[Dict]: + history = [] + random.seed(42) + + price_line = BASE_PRICE + for i in range(days, -1, -1): + date = (datetime.now() - timedelta(days=i)).strftime("%Y-%m-%d") + date_seed = _daily_seed(date) + + drift = (date_seed - 0.5) * TREND_DRIFT + noise = (date_seed - 0.5) * PRICE_VOLATILITY * 1.5 + price_line = price_line + drift + noise * 0.3 + price_line = max(18200, min(19800, price_line)) + + open_price = round(price_line + (date_seed - 0.5) * 80, 0) + high_price = round(open_price + abs(date_seed - 0.5) * 160, 0) + low_price = round(open_price - abs(date_seed - 0.5) * 140, 0) + close_price = round(price_line, 0) + + history.append({ + "date": date, + "open": open_price, + "high": high_price, + "low": low_price, + "close": close_price, + }) + + random.seed() + return history diff --git a/src/services/verification_service.py b/src/services/verification_service.py index a914868..725113f 100644 --- a/src/services/verification_service.py +++ b/src/services/verification_service.py @@ -294,7 +294,7 @@ class GeometryVerificationService: from OCC.Core.GProp import GProp_GProps from OCC.Core.BRepGProp import brepgprop_VolumeProperties, brepgprop_SurfaceProperties from OCC.Core.Bnd import Bnd_Box - from OCC.Core.BRepBndLib import brepbndlib_Add + from OCC.Core.BRepBndLib import brepbndlib from OCC.Core.TopExp import TopExp_Explorer from OCC.Core.TopAbs import TopAbs_FACE, TopAbs_EDGE, TopAbs_VERTEX, TopAbs_SOLID @@ -327,7 +327,7 @@ class GeometryVerificationService: # 计算边界框 bbox = Bnd_Box() - brepbndlib_Add(shape, bbox) + brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() # 拓扑统计 @@ -393,7 +393,7 @@ class GeometryVerificationService: from OCC.Core.GProp import GProp_GProps from OCC.Core.BRepGProp import brepgprop_VolumeProperties, brepgprop_SurfaceProperties from OCC.Core.Bnd import Bnd_Box - from OCC.Core.BRepBndLib import brepbndlib_Add + from OCC.Core.BRepBndLib import brepbndlib from OCC.Core.TopExp import TopExp_Explorer from OCC.Core.TopAbs import TopAbs_FACE, TopAbs_EDGE, TopAbs_VERTEX, TopAbs_SOLID @@ -410,7 +410,7 @@ class GeometryVerificationService: # 计算边界框 bbox = Bnd_Box() - brepbndlib_Add(shape, bbox) + brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() # 拓扑统计 diff --git a/static/index.html b/static/index.html index d8b4cd2..0356fa8 100644 --- a/static/index.html +++ b/static/index.html @@ -76,7 +76,8 @@ + - + diff --git a/static/style.css b/static/style.css index a1fd065..257e65a 100644 --- a/static/style.css +++ b/static/style.css @@ -1538,6 +1538,149 @@ optgroup { color: var(--text-tertiary); } +/* ================================ + 铝金属价格板块 + ================================ */ +.aluminum-section { + margin-bottom: var(--space-6); +} + +.aluminum-price-card { + border-left: 4px solid var(--primary-500); +} + +.aluminum-icon { + font-size: 1.2em; +} + +.aluminum-source { + font-size: var(--text-xs); + color: var(--text-muted); +} + +.aluminum-content { + display: flex; + flex-direction: column; + gap: var(--space-4); +} + +.aluminum-price-row { + display: flex; + align-items: baseline; + gap: var(--space-6); + flex-wrap: wrap; +} + +.aluminum-current { + display: flex; + align-items: baseline; + gap: var(--space-3); +} + +.aluminum-price-value { + font-size: 2.5rem; + font-weight: var(--font-bold); + color: var(--text-primary); + line-height: 1.2; + font-variant-numeric: tabular-nums; +} + +.aluminum-price-unit { + font-size: var(--text-sm); + color: var(--text-tertiary); +} + +.aluminum-change { + display: flex; + align-items: center; + gap: var(--space-1); + font-weight: var(--font-semibold); + font-size: var(--text-base); +} + +.aluminum-change.price-up { + color: #dc2626; +} + +.aluminum-change.price-down { + color: #16a34a; +} + +.change-arrow { + font-size: var(--text-sm); +} + +.aluminum-detail-row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: var(--space-3); + padding: var(--space-4); + background: var(--bg-secondary); + border-radius: var(--radius-lg); +} + +.aluminum-detail-item { + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.detail-label { + font-size: var(--text-xs); + color: var(--text-tertiary); +} + +.detail-value { + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--text-primary); + font-variant-numeric: tabular-nums; +} + +.price-up-text { + color: #dc2626; +} + +.price-down-text { + color: #16a34a; +} + +.aluminum-chart-wrapper { + margin-top: var(--space-2); +} + +.aluminum-chart-title { + font-size: var(--text-sm); + font-weight: var(--font-medium); + color: var(--text-secondary); + margin-bottom: var(--space-3); +} + +.aluminum-chart-container { + position: relative; + width: 100%; + height: 280px; + background: var(--bg-secondary); + border-radius: var(--radius-lg); + padding: var(--space-3); +} + +@media (max-width: 768px) { + .aluminum-price-value { + font-size: 1.75rem; + } + .aluminum-price-row { + gap: var(--space-3); + } + .aluminum-detail-row { + grid-template-columns: repeat(2, 1fr); + gap: var(--space-2); + } + .aluminum-chart-container { + height: 220px; + } +} + /* ================================ 响应式设计 ================================ */ diff --git a/static/vue-app.js b/static/vue-app.js index 8940659..5aedf35 100644 --- a/static/vue-app.js +++ b/static/vue-app.js @@ -463,7 +463,10 @@ const HomeView = { const state = reactive({ stats: null, loading: true, - backendDbReady: true + backendDbReady: true, + aluminumPrice: null, + aluminumHistory: [], + aluminumLoading: true }); const loadStats = async () => { @@ -481,15 +484,94 @@ const HomeView = { } }; + const loadAluminumPrice = async () => { + try { + const [current, history] = await Promise.all([ + apiRequest('/api/aluminum-price/current').catch(() => null), + apiRequest('/api/aluminum-price/history?days=30').catch(() => null) + ]); + state.aluminumPrice = current; + state.aluminumHistory = history || []; + } catch (e) { + console.error('铝价数据加载失败:', e); + } finally { + state.aluminumLoading = false; + nextTick(() => { + renderAluminumChart(); + }); + } + }; + + let chartInstance = null; + const renderAluminumChart = () => { + const canvas = document.getElementById('aluminumChart'); + if (!canvas || !state.aluminumHistory.length) return; + if (chartInstance) chartInstance.destroy(); + const ctx = canvas.getContext('2d'); + const labels = state.aluminumHistory.map(d => d.date.slice(5)); + const prices = state.aluminumHistory.map(d => d.close); + const gradient = ctx.createLinearGradient(0, 0, 0, 280); + gradient.addColorStop(0, 'rgba(59, 130, 246, 0.35)'); + gradient.addColorStop(1, 'rgba(59, 130, 246, 0.02)'); + chartInstance = new Chart(ctx, { + type: 'line', + data: { + labels, + datasets: [{ + label: '铝价 (元/吨)', + data: prices, + borderColor: '#3b82f6', + backgroundColor: gradient, + borderWidth: 2, + fill: true, + tension: 0.3, + pointRadius: 0, + pointHoverRadius: 5, + pointHoverBackgroundColor: '#3b82f6', + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + interaction: { mode: 'index', intersect: false }, + plugins: { + legend: { display: false }, + tooltip: { + backgroundColor: 'rgba(23, 23, 23, 0.9)', + titleFont: { size: 12 }, + bodyFont: { size: 13 }, + padding: 10, + cornerRadius: 8, + displayColors: false, + callbacks: { + label: ctx => '¥' + ctx.parsed.y.toLocaleString() + ' 元/吨' + } + } + }, + scales: { + x: { + grid: { display: false }, + ticks: { color: '#a3a3a3', font: { size: 10 }, maxTicksLimit: 8 } + }, + y: { + grid: { color: 'rgba(0,0,0,0.05)' }, + ticks: { color: '#a3a3a3', font: { size: 10 }, callback: v => v.toLocaleString() } + } + } + } + }); + }; + onMounted(() => { if (!appState.user) { router.push('/login'); return; } loadStats(); + loadAluminumPrice(); }); - return { state, formatNumber, formatCurrency, appState }; + return { state, formatNumber, formatCurrency, appState, loadAluminumPrice }; }, template: `
@@ -552,6 +634,64 @@ const HomeView = {
+ +
+
+
+
+ 🪨 + 铝金属实时行情 +
+
数据来源: SHFE模拟
+
+
+
+
+

加载铝价数据...

+
+
+
+
+
¥{{ state.aluminumPrice.price?.toLocaleString() }}
+
{{ state.aluminumPrice.unit }}
+
+
+ {{ state.aluminumPrice.change >= 0 ? '▲' : '▼' }} + {{ Math.abs(state.aluminumPrice.change)?.toLocaleString() }} + ({{ state.aluminumPrice.change_percent >= 0 ? '+' : '' }}{{ state.aluminumPrice.change_percent }}%) +
+
+
+
+ 开盘价 + ¥{{ state.aluminumPrice.open?.toLocaleString() }} +
+
+ 最高价 + ¥{{ state.aluminumPrice.high?.toLocaleString() }} +
+
+ 最低价 + ¥{{ state.aluminumPrice.low?.toLocaleString() }} +
+
+ 昨收价 + ¥{{ state.aluminumPrice.prev_close?.toLocaleString() }} +
+
+
+
近30日价格走势
+
+ +
+
+
+
+

铝价数据暂不可用

+
+
+
+

低库存预警