412 lines
10 KiB
Vue
412 lines
10 KiB
Vue
<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> |