calc/frontend/src/views/PriceCalculator.vue

412 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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="区域">
<el-select
v-model="form.region"
placeholder="请选择区域"
class="full-width"
filterable
clearable
:filter-method="filterRegions"
:remote-method="filterRegions">
<el-option
v-for="region in filteredRegions"
: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: [],
filteredRegions: [],
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)
this.filteredRegions = [...this.regions] // 初始化筛选后的区域列表
} catch (error) {
console.error('Error fetching data:', error)
this.$message.error('获取数据失败')
}
},
methods: {
filterRegions(query) {
if (query) {
this.filteredRegions = this.regions.filter(region => {
return region.toLowerCase().includes(query.toLowerCase())
})
} else {
this.filteredRegions = [...this.regions]
}
},
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>