e728dcd226
① D11 HTML 报告 RustFS 单源化(TECH_DEBT P2 清偿):可视化产物写任务临时目录后
裸传报告键 html/reports/{filename}(文件名寻址),/html StaticFiles 挂载删除,
新增 html_report_router 根路径代理(报告键→遗留 JSON 包装→本地卷兜底→404,
防穿越);URL 形状 /html/{filename} 不变,持久化引用零迁移;celery 摘除
html_data 卷,镜像不再烤入陈旧报告;顺带删除 get_stp_file_with_data 死数据块
② OCC 方案 B(D10 清偿):run_occ(op_name, payload) 契约 + 常驻工作进程池
(occ_process_pool + occ_worker 操作注册表),超时/崩溃 terminate 换新补位、
任务级超时 recover 整体重建,残留线程泄漏根治;TopoDS 不跨进程(generate_cavity
分模 + 方案 STEP 持久化全在子进程内,返回 export_manifest);删除内存形状缓存链、
CADExporter.export_mold_results、shape_loader(→ stp_materializer)
③ OCC 方案 A 部署参数:CELERY_CONCURRENCY / CELERY_MAX_TASKS_PER_CHILD 进
Dockerfile.celery + compose + .env.example
④ D2 诚实标注:铝价响应带 source: "simulated",前端按来源渲染标注(原硬编码
"上海期货交易所"属虚假声明),死代码 getAluminumPrice 删除
⑤ CI 门禁:.gitea/workflows/ci.yml 三 job(pytest / 前端构建含 vue-tsc /
openapi 漂移检测)
接口变更三件套随批完成(openapi 76→77 paths + gen:api + 前端构建通过;方案 B
接口面零变化)。测试基线 143 passed, 0 skipped(新增 16 项)。文档六处同步。
Co-Authored-By: Claude Code <noreply@anthropic.com>
235 lines
8.8 KiB
Vue
235 lines
8.8 KiB
Vue
<template>
|
||
<div>
|
||
<div class="hero-section">
|
||
<h1 class="hero-title">Gemold 模具制造管理系统</h1>
|
||
<p class="hero-subtitle">智能模具设计与制造一体化平台,从零件分析到模具生成,全流程数字化解决方案</p>
|
||
</div>
|
||
|
||
<div class="aluminum-section" v-if="aluminum.price">
|
||
<div class="card aluminum-price-card">
|
||
<div class="card-header">
|
||
<div class="card-title">
|
||
<span class="aluminum-icon">🪙</span> 铝金属价格
|
||
</div>
|
||
<span class="aluminum-source">{{ aluminumSourceLabel }}</span>
|
||
</div>
|
||
<div class="aluminum-content">
|
||
<div class="aluminum-price-row">
|
||
<div class="aluminum-current">
|
||
<span class="aluminum-price-value">{{ formatAluminumPrice(aluminum.price) }}</span>
|
||
<span class="aluminum-price-unit">元/吨</span>
|
||
</div>
|
||
<div :class="['aluminum-change', aluminum.change >= 0 ? 'price-up' : 'price-down']">
|
||
<span class="change-arrow">{{ aluminum.change >= 0 ? '▲' : '▼' }}</span>
|
||
<span>{{ formatAluminumPrice(Math.abs(aluminum.change)) }}</span>
|
||
<span>({{ aluminum.change_percent >= 0 ? '+' : '' }}{{ aluminum.change_percent }}%)</span>
|
||
</div>
|
||
</div>
|
||
<div class="aluminum-detail-row">
|
||
<div class="aluminum-detail-item">
|
||
<span class="detail-label">开盘价</span>
|
||
<span class="detail-value">{{ formatAluminumPrice(aluminum.open) }}</span>
|
||
</div>
|
||
<div class="aluminum-detail-item">
|
||
<span class="detail-label">最高价</span>
|
||
<span class="detail-value">{{ formatAluminumPrice(aluminum.high) }}</span>
|
||
</div>
|
||
<div class="aluminum-detail-item">
|
||
<span class="detail-label">最低价</span>
|
||
<span class="detail-value">{{ formatAluminumPrice(aluminum.low) }}</span>
|
||
</div>
|
||
<div class="aluminum-detail-item">
|
||
<span class="detail-label">昨收价</span>
|
||
<span class="detail-value">{{ formatAluminumPrice(aluminum.prev_close) }}</span>
|
||
</div>
|
||
<div class="aluminum-detail-item">
|
||
<span class="detail-label">7日前</span>
|
||
<span class="detail-value">{{ formatAluminumPrice(aluminum.week_ago_price) }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="aluminum-chart-wrapper" v-if="aluminum.history.length > 0">
|
||
<div class="aluminum-chart-title">近30日价格走势</div>
|
||
<div class="aluminum-chart-container">
|
||
<canvas ref="chartCanvas"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="features-grid">
|
||
<div class="feature-card" @click="router.push('/moldinsight')" style="cursor: pointer;">
|
||
<div class="feature-icon">◈</div>
|
||
<h3 class="feature-title">MoldInsight</h3>
|
||
<p class="feature-desc">智能模具分析引擎,支持 STP/STEP 文件解析、分模设计、CAM 刀路规划</p>
|
||
</div>
|
||
<div class="feature-card" @click="router.push('/inventory')" style="cursor: pointer;">
|
||
<div class="feature-icon">⊞</div>
|
||
<h3 class="feature-title">进销存管理</h3>
|
||
<p class="feature-desc">产品管理、采购销售、库存管理、财务管理,一站式 ERP 解决方案</p>
|
||
</div>
|
||
<div class="feature-card">
|
||
<div class="feature-icon">⚙</div>
|
||
<h3 class="feature-title">AI 驱动</h3>
|
||
<p class="feature-desc">AI 分型面检测、模具方案评分、智能设计建议,提升设计效率</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, reactive, onMounted, nextTick, ref } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { apiRequest } from '@/shared/api'
|
||
import { Chart, registerables } from 'chart.js'
|
||
|
||
const router = useRouter()
|
||
const chartCanvas = ref<HTMLCanvasElement | null>(null)
|
||
let chartInstance: Chart | null = null
|
||
|
||
interface AluminumPrice {
|
||
price: number
|
||
unit: string
|
||
date: string
|
||
change: number
|
||
change_percent: number
|
||
open: number
|
||
high: number
|
||
low: number
|
||
prev_close: number
|
||
week_ago_price: number
|
||
// D2:数据来源声明——simulated 为模拟走势(参考数据,非实时行情)
|
||
source?: string
|
||
}
|
||
|
||
interface HistoryItem {
|
||
date: string
|
||
close: number
|
||
}
|
||
|
||
const aluminum = reactive({
|
||
price: null as number | null,
|
||
unit: '',
|
||
date: '',
|
||
change: 0,
|
||
change_percent: 0,
|
||
open: 0,
|
||
source: '',
|
||
high: 0,
|
||
low: 0,
|
||
prev_close: 0,
|
||
week_ago_price: 0,
|
||
history: [] as HistoryItem[],
|
||
})
|
||
|
||
const formatAluminumPrice = (val: number | null) => {
|
||
if (val === null || val === undefined) return '--'
|
||
return Math.round(val).toLocaleString('zh-CN')
|
||
}
|
||
|
||
// D2:来源标注以接口 source 字段为准,不再硬编码交易所名称——
|
||
// 此前显示"数据来源: 上海期货交易所"而数据实为模拟走势,属虚假来源声明
|
||
const aluminumSourceLabel = computed(() => {
|
||
if (aluminum.source === 'simulated') return '模拟数据 · 参考走势,非实时行情'
|
||
if (aluminum.source) return `数据来源: ${aluminum.source}`
|
||
return `更新于 ${aluminum.date}`
|
||
})
|
||
|
||
const loadAluminumPrice = async () => {
|
||
try {
|
||
const data = await apiRequest<AluminumPrice>('/api/aluminum-price/current')
|
||
aluminum.price = data.price
|
||
aluminum.unit = data.unit
|
||
aluminum.date = data.date
|
||
aluminum.source = data.source ?? ''
|
||
aluminum.change = data.change
|
||
aluminum.change_percent = data.change_percent
|
||
aluminum.open = data.open
|
||
aluminum.high = data.high
|
||
aluminum.low = data.low
|
||
aluminum.prev_close = data.prev_close
|
||
aluminum.week_ago_price = data.week_ago_price
|
||
} catch (e) {
|
||
console.error('加载铝价失败:', e)
|
||
}
|
||
|
||
try {
|
||
const history = await apiRequest<HistoryItem[]>('/api/aluminum-price/history?days=30')
|
||
aluminum.history = history
|
||
await nextTick()
|
||
renderChart()
|
||
} catch (e) {
|
||
console.error('加载铝价历史失败:', e)
|
||
}
|
||
}
|
||
|
||
const renderChart = () => {
|
||
if (!chartCanvas.value || aluminum.history.length === 0) return
|
||
if (chartInstance) chartInstance.destroy()
|
||
|
||
const isUp = aluminum.change >= 0
|
||
const lineColor = isUp ? '#dc2626' : '#16a34a'
|
||
const fillColor = isUp ? 'rgba(220, 38, 38, 0.08)' : 'rgba(22, 163, 74, 0.08)'
|
||
|
||
chartInstance = new Chart(chartCanvas.value, {
|
||
type: 'line',
|
||
data: {
|
||
labels: aluminum.history.map(h => h.date.slice(5)),
|
||
datasets: [{
|
||
label: '收盘价 (元/吨)',
|
||
data: aluminum.history.map(h => h.close),
|
||
borderColor: lineColor,
|
||
backgroundColor: fillColor,
|
||
fill: true,
|
||
tension: 0.3,
|
||
pointRadius: 0,
|
||
borderWidth: 2,
|
||
}],
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
legend: { display: false },
|
||
tooltip: {
|
||
callbacks: {
|
||
label: (ctx) => `${Math.round(ctx.parsed.y ?? 0).toLocaleString('zh-CN')} 元/吨`,
|
||
},
|
||
},
|
||
},
|
||
scales: {
|
||
x: {
|
||
grid: { display: false },
|
||
ticks: { maxTicksLimit: 10, font: { size: 10 }, color: 'var(--text-tertiary)' },
|
||
},
|
||
y: {
|
||
grid: { color: 'var(--border-light)' },
|
||
ticks: {
|
||
font: { size: 10 },
|
||
color: 'var(--text-tertiary)',
|
||
callback: (v) => (v as number).toLocaleString('zh-CN'),
|
||
},
|
||
},
|
||
},
|
||
},
|
||
})
|
||
}
|
||
|
||
onMounted(() => {
|
||
Chart.register(...registerables)
|
||
loadAluminumPrice()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.hero-section { text-align: center; padding: var(--space-16) 0; }
|
||
.hero-title { font-size: var(--text-4xl); font-weight: var(--font-bold); color: var(--text-primary); margin-bottom: var(--space-4); }
|
||
.hero-subtitle { font-size: var(--text-lg); color: var(--text-tertiary); max-width: 600px; margin: 0 auto var(--space-8); }
|
||
.features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--space-6); margin-top: var(--space-12); }
|
||
.feature-card { background: var(--bg-primary); border: 1px solid var(--border-light); border-radius: var(--radius-xl); padding: var(--space-6); text-align: left; }
|
||
.feature-card[style*="cursor"]:hover { border-color: var(--primary-200); box-shadow: var(--shadow-md); transform: translateY(-2px); transition: all 0.2s ease; }
|
||
.feature-icon { width: 40px; height: 40px; background: var(--primary-50); border-radius: var(--radius-lg); display: flex; align-items: center; justify-content: center; color: var(--primary-500); font-size: var(--text-lg); margin-bottom: var(--space-4); }
|
||
.feature-title { font-size: var(--text-base); font-weight: var(--font-semibold); color: var(--text-primary); margin-bottom: var(--space-2); }
|
||
.feature-desc { font-size: var(--text-sm); color: var(--text-tertiary); line-height: 1.6; }
|
||
</style>
|