修复铝价格走势

This commit is contained in:
2026-06-01 14:52:47 +08:00
parent c6f1901703
commit ecc6b8ab8a
14 changed files with 203 additions and 14 deletions
+189 -2
View File
@@ -4,13 +4,66 @@
<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> 铝金属价格 (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>
<div class="features-grid">
<div class="feature-card">
<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">
<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>
@@ -24,12 +77,146 @@
</div>
</template>
<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>
<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; }
@@ -1 +1 @@
import{E as e,_ as t,t as n,y as r}from"./index-CpJd5y_S.js";var i={};function a(n,i){return e(),r(`div`,null,[...i[0]||=[t(`div`,{class:`page-header`},[t(`h1`,{class:`page-title`},`设计体系`),t(`p`,{class:`page-subtitle`},`迁移中...`)],-1)]])}var o=n(i,[[`render`,a]]);export{o as default};
import{E as e,_ as t,t as n,y as r}from"./index-SE6ZBOjs.js";var i={};function a(n,i){return e(),r(`div`,null,[...i[0]||=[t(`div`,{class:`page-header`},[t(`h1`,{class:`page-title`},`设计体系`),t(`p`,{class:`page-subtitle`},`迁移中...`)],-1)]])}var o=n(i,[[`render`,a]]);export{o as default};
+1
View File
@@ -0,0 +1 @@
.hero-section[data-v-1a4785c1]{text-align:center;padding:var(--space-16) 0}.hero-title[data-v-1a4785c1]{font-size:var(--text-4xl);font-weight:var(--font-bold);color:var(--text-primary);margin-bottom:var(--space-4)}.hero-subtitle[data-v-1a4785c1]{font-size:var(--text-lg);color:var(--text-tertiary);max-width:600px;margin:0 auto var(--space-8)}.features-grid[data-v-1a4785c1]{gap:var(--space-6);margin-top:var(--space-12);grid-template-columns:repeat(auto-fit,minmax(280px,1fr));display:grid}.feature-card[data-v-1a4785c1]{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][data-v-1a4785c1]:hover{border-color:var(--primary-200);box-shadow:var(--shadow-md);transition:all .2s;transform:translateY(-2px)}.feature-icon[data-v-1a4785c1]{background:var(--primary-50);border-radius:var(--radius-lg);width:40px;height:40px;color:var(--primary-500);font-size:var(--text-lg);margin-bottom:var(--space-4);justify-content:center;align-items:center;display:flex}.feature-title[data-v-1a4785c1]{font-size:var(--text-base);font-weight:var(--font-semibold);color:var(--text-primary);margin-bottom:var(--space-2)}.feature-desc[data-v-1a4785c1]{font-size:var(--text-sm);color:var(--text-tertiary);line-height:1.6}
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
.hero-section[data-v-a374362f]{text-align:center;padding:var(--space-16) 0}.hero-title[data-v-a374362f]{font-size:var(--text-4xl);font-weight:var(--font-bold);color:var(--text-primary);margin-bottom:var(--space-4)}.hero-subtitle[data-v-a374362f]{font-size:var(--text-lg);color:var(--text-tertiary);max-width:600px;margin:0 auto var(--space-8)}.features-grid[data-v-a374362f]{gap:var(--space-6);margin-top:var(--space-12);grid-template-columns:repeat(auto-fit,minmax(280px,1fr));display:grid}.feature-card[data-v-a374362f]{background:var(--bg-primary);border:1px solid var(--border-light);border-radius:var(--radius-xl);padding:var(--space-6);text-align:left}.feature-icon[data-v-a374362f]{background:var(--primary-50);border-radius:var(--radius-lg);width:40px;height:40px;color:var(--primary-500);font-size:var(--text-lg);margin-bottom:var(--space-4);justify-content:center;align-items:center;display:flex}.feature-title[data-v-a374362f]{font-size:var(--text-base);font-weight:var(--font-semibold);color:var(--text-primary);margin-bottom:var(--space-2)}.feature-desc[data-v-a374362f]{font-size:var(--text-sm);color:var(--text-tertiary);line-height:1.6}
-1
View File
@@ -1 +0,0 @@
import{E as e,b as t,t as n,y as r}from"./index-CpJd5y_S.js";var i={};function a(n,i){return e(),r(`div`,null,[...i[0]||=[t(`<div class="hero-section" data-v-a374362f><h1 class="hero-title" data-v-a374362f>Gemold 模具制造管理系统</h1><p class="hero-subtitle" data-v-a374362f>智能模具设计与制造一体化平台,从零件分析到模具生成,全流程数字化解决方案</p></div><div class="features-grid" data-v-a374362f><div class="feature-card" data-v-a374362f><div class="feature-icon" data-v-a374362f>◈</div><h3 class="feature-title" data-v-a374362f>MoldInsight</h3><p class="feature-desc" data-v-a374362f>智能模具分析引擎,支持 STP/STEP 文件解析、分模设计、CAM 刀路规划</p></div><div class="feature-card" data-v-a374362f><div class="feature-icon" data-v-a374362f>⊞</div><h3 class="feature-title" data-v-a374362f>进销存管理</h3><p class="feature-desc" data-v-a374362f>产品管理、采购销售、库存管理、财务管理,一站式 ERP 解决方案</p></div><div class="feature-card" data-v-a374362f><div class="feature-icon" data-v-a374362f>⚙</div><h3 class="feature-title" data-v-a374362f>AI 驱动</h3><p class="feature-desc" data-v-a374362f>AI 分型面检测、模具方案评分、智能设计建议,提升设计效率</p></div></div>`,2)]])}var o=n(i,[[`render`,a],[`__scopeId`,`data-v-a374362f`]]);export{o as default};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{E as e,O as t,P as n,S as r,_ as i,a,f as o,k as s,l as c,m as l,n as u,o as d,t as f,v as p,y as m}from"./index-CpJd5y_S.js";var h={class:`login-container`},g={class:`login-card`},_={class:`form-group`},v={class:`form-group`},y={key:0,class:`form-error`},b=[`disabled`],x=f(r({__name:`LoginView`,setup(r){let f=c(),x=s({username:``,password:``,error:``,loading:!1}),S=async()=>{x.error=``,x.loading=!0;try{let e=await d(`/api/auth/login/json`,{method:`POST`,body:JSON.stringify({username:x.username,password:x.password})});a(e.access_token,e.user),u(`登录成功`,`success`),f.push(`/`)}catch(e){x.error=e.message||`登录失败`}finally{x.loading=!1}};return(r,a)=>(e(),m(`div`,h,[i(`div`,g,[a[4]||=i(`div`,{class:`login-header`},[i(`div`,{class:`login-logo`},`G`),i(`h1`,{class:`login-title`},`Gemold`),i(`p`,{class:`login-subtitle`},`模具制造管理系统`)],-1),i(`form`,{onSubmit:l(S,[`prevent`])},[i(`div`,_,[a[2]||=i(`label`,{class:`form-label`},`用户名`,-1),t(i(`input`,{"onUpdate:modelValue":a[0]||=e=>x.username=e,type:`text`,class:`form-input`,placeholder:`请输入用户名`,required:``},null,512),[[o,x.username]])]),i(`div`,v,[a[3]||=i(`label`,{class:`form-label`},`密码`,-1),t(i(`input`,{"onUpdate:modelValue":a[1]||=e=>x.password=e,type:`password`,class:`form-input`,placeholder:`请输入密码`,required:``},null,512),[[o,x.password]])]),x.error?(e(),m(`div`,y,n(x.error),1)):p(``,!0),i(`button`,{type:`submit`,class:`btn btn-primary w-full mt-4`,disabled:x.loading},n(x.loading?`登录中...`:`登录`),9,b)],32)])]))}}),[[`__scopeId`,`data-v-01da5b35`]]);export{x as default};
import{E as e,O as t,P as n,S as r,_ as i,a,f as o,k as s,l as c,m as l,n as u,o as d,t as f,v as p,y as m}from"./index-SE6ZBOjs.js";var h={class:`login-container`},g={class:`login-card`},_={class:`form-group`},v={class:`form-group`},y={key:0,class:`form-error`},b=[`disabled`],x=f(r({__name:`LoginView`,setup(r){let f=c(),x=s({username:``,password:``,error:``,loading:!1}),S=async()=>{x.error=``,x.loading=!0;try{let e=await d(`/api/auth/login/json`,{method:`POST`,body:JSON.stringify({username:x.username,password:x.password})});a(e.access_token,e.user),u(`登录成功`,`success`),f.push(`/`)}catch(e){x.error=e.message||`登录失败`}finally{x.loading=!1}};return(r,a)=>(e(),m(`div`,h,[i(`div`,g,[a[4]||=i(`div`,{class:`login-header`},[i(`div`,{class:`login-logo`},`G`),i(`h1`,{class:`login-title`},`Gemold`),i(`p`,{class:`login-subtitle`},`模具制造管理系统`)],-1),i(`form`,{onSubmit:l(S,[`prevent`])},[i(`div`,_,[a[2]||=i(`label`,{class:`form-label`},`用户名`,-1),t(i(`input`,{"onUpdate:modelValue":a[0]||=e=>x.username=e,type:`text`,class:`form-input`,placeholder:`请输入用户名`,required:``},null,512),[[o,x.username]])]),i(`div`,v,[a[3]||=i(`label`,{class:`form-label`},`密码`,-1),t(i(`input`,{"onUpdate:modelValue":a[1]||=e=>x.password=e,type:`password`,class:`form-input`,placeholder:`请输入密码`,required:``},null,512),[[o,x.password]])]),x.error?(e(),m(`div`,y,n(x.error),1)):p(``,!0),i(`button`,{type:`submit`,class:`btn btn-primary w-full mt-4`,disabled:x.loading},n(x.loading?`登录中...`:`登录`),9,b)],32)])]))}}),[[`__scopeId`,`data-v-01da5b35`]]);export{x as default};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{E as e,_ as t,t as n,y as r}from"./index-CpJd5y_S.js";var i={};function a(n,i){return e(),r(`div`,null,[...i[0]||=[t(`div`,{class:`page-header`},[t(`h1`,{class:`page-title`},`灰度发布与回滚`),t(`p`,{class:`page-subtitle`},`迁移中...`)],-1)]])}var o=n(i,[[`render`,a]]);export{o as default};
import{E as e,_ as t,t as n,y as r}from"./index-SE6ZBOjs.js";var i={};function a(n,i){return e(),r(`div`,null,[...i[0]||=[t(`div`,{class:`page-header`},[t(`h1`,{class:`page-title`},`灰度发布与回滚`),t(`p`,{class:`page-subtitle`},`迁移中...`)],-1)]])}var o=n(i,[[`render`,a]]);export{o as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -61,7 +61,7 @@
} catch (e) {}
})();
</script>
<script type="module" crossorigin src="/static/assets/index-CpJd5y_S.js"></script>
<script type="module" crossorigin src="/static/assets/index-SE6ZBOjs.js"></script>
<link rel="stylesheet" crossorigin href="/static/assets/index-BBu9hxSF.css">
</head>
<body>