Files
geMoldInsight/frontend/src/modules/home/HomeView.vue
T

224 lines
8.3 KiB
Vue
Raw Normal View History

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>