2026-05-29 17:30:26 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div class="hero-section">
|
|
|
|
|
|
<h1 class="hero-title">Gemold 模具制造管理系统</h1>
|
|
|
|
|
|
<p class="hero-subtitle">智能模具设计与制造一体化平台,从零件分析到模具生成,全流程数字化解决方案</p>
|
|
|
|
|
|
</div>
|
2026-06-01 14:52:47 +08:00
|
|
|
|
|
|
|
|
|
|
<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> 铝金属价格 (SHFE)
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="aluminum-source">数据来源: 上海期货交易所 | 更新于 {{ aluminum.date }}</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>
|
|
|
|
|
|
|
2026-05-29 17:30:26 +08:00
|
|
|
|
<div class="features-grid">
|
2026-06-01 14:52:47 +08:00
|
|
|
|
<div class="feature-card" @click="router.push('/moldinsight')" style="cursor: pointer;">
|
2026-05-29 17:30:26 +08:00
|
|
|
|
<div class="feature-icon">◈</div>
|
|
|
|
|
|
<h3 class="feature-title">MoldInsight</h3>
|
|
|
|
|
|
<p class="feature-desc">智能模具分析引擎,支持 STP/STEP 文件解析、分模设计、CAM 刀路规划</p>
|
|
|
|
|
|
</div>
|
2026-06-01 14:52:47 +08:00
|
|
|
|
<div class="feature-card" @click="router.push('/inventory')" style="cursor: pointer;">
|
2026-05-29 17:30:26 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
|
2026-06-01 14:52:47 +08:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { reactive, onMounted, nextTick, ref } from 'vue'
|
|
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
|
import { apiRequest } from '@/shared/api'
|
|
|
|
|
|
import { Chart, registerables } from 'chart.js'
|
|
|
|
|
|
|
|
|
|
|
|
Chart.register(...registerables)
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface HistoryItem {
|
|
|
|
|
|
date: string
|
|
|
|
|
|
close: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const aluminum = reactive({
|
|
|
|
|
|
price: null as number | null,
|
|
|
|
|
|
unit: '',
|
|
|
|
|
|
date: '',
|
|
|
|
|
|
change: 0,
|
|
|
|
|
|
change_percent: 0,
|
|
|
|
|
|
open: 0,
|
|
|
|
|
|
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')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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.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(() => {
|
|
|
|
|
|
loadAluminumPrice()
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
2026-05-29 17:30:26 +08:00
|
|
|
|
<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; }
|
2026-06-01 14:52:47 +08:00
|
|
|
|
.feature-card[style*="cursor"]:hover { border-color: var(--primary-200); box-shadow: var(--shadow-md); transform: translateY(-2px); transition: all 0.2s ease; }
|
2026-05-29 17:30:26 +08:00
|
|
|
|
.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>
|