calc/frontend/src/views/PriceCalculator.vue

412 lines
10 KiB
Vue
Raw Normal View History

2025-03-26 12:48:43 +08:00
<template>
<div class="price-calculator">
<el-card class="calculator-card">
<template #header>
<div class="card-header">
<span><i class="el-icon-s-finance"></i> EC2 价格计算器</span>
<div class="card-subtitle">选择您的实例配置即时获取价格</div>
</div>
</template>
<div class="form-container">
<el-form :model="form" label-width="120px">
<el-row :gutter="20">
<el-col :md="12" :sm="24">
<el-form-item label="实例类型">
<el-select v-model="form.instance_type" placeholder="请选择实例类型" class="full-width">
<el-option
v-for="type in instanceTypes"
:key="type"
:label="type"
:value="type">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :md="12" :sm="24">
<el-form-item label="区域">
2025-04-02 21:51:36 +08:00
<el-select
v-model="form.region"
placeholder="请选择区域"
class="full-width"
filterable
clearable
:filter-method="filterRegions"
:remote-method="filterRegions">
2025-03-26 12:48:43 +08:00
<el-option
2025-04-02 21:51:36 +08:00
v-for="region in filteredRegions"
2025-03-26 12:48:43 +08:00
:key="region"
:label="region"
:value="region">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :md="12" :sm="24">
<el-form-item label="操作系统">
<el-select v-model="form.operating_system" placeholder="请选择操作系统" class="full-width">
<el-option label="Linux" value="Linux">
<div class="option-with-icon">
<span class="option-icon">🐧</span>
<span>Linux</span>
</div>
</el-option>
<el-option label="Windows" value="Windows">
<div class="option-with-icon">
<span class="option-icon">🪟</span>
<span>Windows</span>
</div>
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :md="12" :sm="24">
<el-form-item label="购买选项">
<el-select v-model="form.purchase_option" placeholder="请选择购买选项" class="full-width">
<el-option label="按需实例" value="OnDemand">
<div class="option-with-icon">
<span class="option-icon"></span>
<span>按需实例</span>
</div>
</el-option>
<el-option label="预留实例" value="Reserved">
<div class="option-with-icon">
<span class="option-icon">📅</span>
<span>预留实例</span>
</div>
</el-option>
<el-option label="Spot实例" value="Spot">
<div class="option-with-icon">
<span class="option-icon">💸</span>
<span>Spot实例</span>
</div>
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :md="12" :sm="24">
<el-form-item label="使用时长(月)">
<el-slider
v-model="form.duration"
:min="1"
:max="36"
:format-tooltip="formatDuration"
:marks="{1: '1个月', 12: '1年', 36: '3年'}"
class="duration-slider">
</el-slider>
</el-form-item>
</el-col>
<el-col :md="12" :sm="24" class="flexible-col">
<el-form-item>
<el-button type="primary" @click="calculatePrice" icon="el-icon-money" class="calculate-button">计算价格</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div v-if="priceResult" class="price-result animated fadeIn">
<h3><i class="el-icon-data-analysis"></i> 价格计算结果</h3>
<el-row :gutter="20">
<el-col :md="8" :sm="24">
<div class="price-card hourly">
<div class="price-icon"></div>
<div class="price-title">每小时价格</div>
<div class="price-amount">${{ priceResult.hourly_price.toFixed(4) }}</div>
</div>
</el-col>
<el-col :md="8" :sm="24">
<div class="price-card monthly">
<div class="price-icon">📅</div>
<div class="price-title">每月价格</div>
<div class="price-amount">${{ priceResult.monthly_price.toFixed(2) }}</div>
</div>
</el-col>
<el-col :md="8" :sm="24">
<div class="price-card total">
<div class="price-icon">💰</div>
<div class="price-title">总价格</div>
<div class="price-amount">${{ priceResult.total_price.toFixed(2) }}</div>
<div class="price-period">{{ form.duration }}个月</div>
</div>
</el-col>
</el-row>
<div class="saving-tip" v-if="form.purchase_option === 'Reserved'">
<i class="el-icon-info"></i> 与按需实例相比预留实例可为您节省高达72%的成本
</div>
<div class="saving-tip" v-else-if="form.purchase_option === 'Spot'">
<i class="el-icon-info"></i> 与按需实例相比Spot实例可为您节省高达90%的成本适合灵活的工作负载
</div>
</div>
</el-card>
</div>
</template>
<script>
import apiService from '../api'
export default {
name: 'PriceCalculator',
data() {
return {
form: {
instance_type: '',
region: '',
operating_system: 'Linux',
purchase_option: 'OnDemand',
duration: 1
},
instanceTypes: [],
regions: [],
2025-04-02 21:51:36 +08:00
filteredRegions: [],
2025-03-26 12:48:43 +08:00
priceResult: null
}
},
async created() {
try {
const [instanceTypes, regions] = await Promise.all([
apiService.getInstanceTypes(),
apiService.getRegions()
])
this.instanceTypes = instanceTypes
this.regions = regions.map(region => region.code)
2025-04-02 21:51:36 +08:00
this.filteredRegions = [...this.regions] // 初始化筛选后的区域列表
2025-03-26 12:48:43 +08:00
} catch (error) {
console.error('Error fetching data:', error)
this.$message.error('获取数据失败')
}
},
methods: {
2025-04-02 21:51:36 +08:00
filterRegions(query) {
if (query) {
this.filteredRegions = this.regions.filter(region => {
return region.toLowerCase().includes(query.toLowerCase())
})
} else {
this.filteredRegions = [...this.regions]
}
},
2025-03-26 12:48:43 +08:00
async calculatePrice() {
try {
this.priceResult = await apiService.calculatePrice(this.form)
} catch (error) {
console.error('Error calculating price:', error)
this.$message.error('计算价格失败')
}
},
formatDuration(val) {
if (val === 1) return '1个月'
if (val === 12) return '1年'
if (val === 36) return '3年'
return `${val}个月`
}
}
}
</script>
<style scoped>
.price-calculator {
max-width: 900px;
margin: 0 auto;
position: relative;
z-index: 1;
}
.calculator-card {
margin-bottom: 20px;
transition: all 0.3s ease;
}
.calculator-card:hover {
transform: translateY(-5px);
}
.card-header {
display: flex;
flex-direction: column;
padding: 5px 0;
}
.card-header span {
font-size: 20px;
font-weight: 600;
}
.card-header i {
margin-right: 8px;
}
.card-subtitle {
margin-top: 5px;
font-size: 14px;
opacity: 0.8;
}
.form-container {
padding: 20px 10px;
}
.full-width {
width: 100%;
}
.option-with-icon {
display: flex;
align-items: center;
}
.option-icon {
margin-right: 8px;
font-size: 16px;
}
.duration-slider {
margin: 15px 0;
}
.calculate-button {
width: 100%;
height: 40px;
font-size: 16px;
transition: all 0.3s ease;
}
.calculate-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3);
}
.price-result {
margin-top: 30px;
padding-top: 20px;
border-top: 1px dashed #e0e0e0;
}
.price-result h3 {
margin-bottom: 20px;
color: #2c3e50;
font-size: 18px;
text-align: center;
}
.price-result h3 i {
margin-right: 8px;
color: var(--secondary-color);
}
.price-card {
background: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 15px;
text-align: center;
transition: all 0.3s ease;
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.05);
height: 100%;
min-height: 160px;
display: flex;
flex-direction: column;
justify-content: center;
}
.price-card:hover {
transform: translateY(-5px);
}
.price-card.hourly {
border-top: 5px solid #3498db;
}
.price-card.monthly {
border-top: 5px solid #f39c12;
}
.price-card.total {
border-top: 5px solid #2ecc71;
}
.price-icon {
font-size: 28px;
margin-bottom: 10px;
}
.price-title {
color: #7f8c8d;
font-size: 16px;
margin-bottom: 10px;
}
.price-amount {
font-size: 28px;
font-weight: 700;
color: #2c3e50;
margin-bottom: 5px;
}
.price-period {
font-size: 14px;
color: #95a5a6;
}
.saving-tip {
margin-top: 20px;
padding: 10px 15px;
background-color: #e5f9e7;
border-radius: 8px;
color: #333;
font-size: 14px;
display: flex;
align-items: center;
}
.saving-tip i {
color: #2ecc71;
font-size: 18px;
margin-right: 10px;
}
.flexible-col {
display: flex;
align-items: center;
}
.animated {
animation-duration: 0.5s;
animation-fill-mode: both;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fadeIn {
animation-name: fadeIn;
}
@media (max-width: 768px) {
.price-calculator {
padding: 0 10px;
}
.calculate-button {
margin-top: 15px;
}
}
</style>