calc/frontend/src/views/InstanceSearch.vue

1084 lines
32 KiB
Vue
Raw Normal View History

2025-03-26 12:48:43 +08:00
<template>
<div class="instance-search">
<el-card class="search-card">
<template #header>
<div class="card-header">
<span><i class="el-icon-search"></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="8" :sm="24">
<el-form-item label="CPU 核心数">
<el-input-number
v-model="form.cpu_cores"
:min="0"
2025-03-26 12:48:43 +08:00
:max="64"
:step="1"
placeholder="所需CPU核心数"
class="full-width">
</el-input-number>
</el-form-item>
</el-col>
<el-col :md="8" :sm="24">
<el-form-item label="内存(GB)">
<el-input-number
v-model="form.memory_gb"
:min="0"
2025-03-26 12:48:43 +08:00
:max="256"
:step="0.5"
placeholder="所需内存容量(GB)"
class="full-width">
</el-input-number>
</el-form-item>
</el-col>
<el-col :md="8" :sm="24">
<el-form-item label="磁盘(GB)">
<el-input-number
v-model="form.disk_gb"
:min="8"
:max="16000"
:step="1"
placeholder="GP3卷存储容量(GB)"
class="full-width">
</el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :md="6" :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.code"
:label="region.name"
:value="region.code">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :md="6" :sm="24">
<el-form-item label="操作系统">
<el-select v-model="form.operating_system" placeholder="选择操作系统" class="full-width">
<el-option label="Linux" value="Linux"></el-option>
<el-option label="Windows" value="Windows"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :md="6" :sm="24">
<el-form-item label="磁盘类型">
<el-select v-model="form.disk_type" disabled placeholder="磁盘类型" class="full-width">
<el-option label="GP3 (通用SSD)" value="gp3"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :md="6" :sm="24" class="flexible-col">
<el-form-item>
<el-button type="primary" @click="searchInstances" icon="el-icon-search" class="search-button">搜索实例</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div v-if="loading" class="loading-container">
<el-skeleton :rows="5" animated />
</div>
<div v-else-if="searchResults.length" class="search-results animated fadeIn">
<h3><i class="el-icon-s-data"></i> 符合条件的实例</h3>
<el-table :data="searchResults" border style="width: 100%" :stripe="true" class="result-table">
<el-table-column prop="instance_type" label="实例类型" width="120">
<template #default="scope">
<div class="instance-type">
<el-tag type="info">{{ scope.row.instance_type }}</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" min-width="180"></el-table-column>
<el-table-column prop="cpu" label="CPU核心" width="100" align="center">
<template #default="scope">
<div class="spec-value">
<i class="el-icon-cpu"></i> {{ scope.row.cpu }}
</div>
</template>
</el-table-column>
<el-table-column prop="memory" label="内存(GB)" width="100" align="center">
<template #default="scope">
<div class="spec-value">
<i class="el-icon-coin"></i> {{ scope.row.memory }}
</div>
</template>
</el-table-column>
<el-table-column prop="disk_gb" label="磁盘(GB)" width="100" align="center">
<template #default="scope">
<div class="spec-value">
<i class="el-icon-folder"></i> {{ scope.row.disk_gb }}
</div>
</template>
</el-table-column>
<el-table-column label="每月价格" width="260">
<template #default="scope">
<div class="price-breakdown">
<div class="price-item">
<span class="price-label">实例:</span>
<span class="price-value">${{ scope.row.monthly_price.toFixed(2) }}</span>
</div>
<div class="price-item">
<span class="price-label">磁盘:</span>
<span class="price-value">${{ scope.row.disk_monthly_price.toFixed(2) }}</span>
</div>
<div class="price-item total">
<span class="price-label">总计:</span>
<span class="price-value highlight">${{ scope.row.total_monthly_price.toFixed(2) }}</span>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template #default="scope">
<div class="action-buttons-cell">
<el-button
type="primary"
size="small"
@click="selectInstance(scope.row)"
icon="el-icon-select">
查看详情
</el-button>
<el-button
type="success"
size="small"
@click="addToComparison(scope.row)"
icon="el-icon-plus">
加入对比
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div v-else-if="searched" class="no-results">
<el-empty description="没有找到符合条件的实例,请尝试降低配置要求"></el-empty>
</div>
<div v-if="selectedInstance" class="selected-instance animated fadeIn">
<h3><i class="el-icon-s-check"></i> 已选择实例</h3>
<el-card class="instance-card">
<el-row :gutter="20">
<el-col :md="4" :sm="24">
<div class="instance-info-item">
<div class="info-label">实例类型</div>
<div class="info-value">{{ selectedInstance.instance_type }}</div>
</div>
</el-col>
<el-col :md="4" :sm="24">
<div class="instance-info-item">
<div class="info-label">CPU核心</div>
<div class="info-value">{{ selectedInstance.cpu }} </div>
</div>
</el-col>
<el-col :md="4" :sm="24">
<div class="instance-info-item">
<div class="info-label">内存</div>
<div class="info-value">{{ selectedInstance.memory }} GB</div>
</div>
</el-col>
<el-col :md="4" :sm="24">
<div class="instance-info-item">
<div class="info-label">磁盘</div>
<div class="info-value">{{ selectedInstance.disk_gb }} GB (GP3)</div>
</div>
</el-col>
<el-col :md="4" :sm="24">
<div class="instance-info-item">
<div class="info-label">区域</div>
<div class="info-value">{{ getRegionName(form.region) }}</div>
</div>
</el-col>
<el-col :md="4" :sm="24">
<div class="instance-info-item">
<div class="info-label">操作系统</div>
<div class="info-value">{{ form.operating_system }}</div>
</div>
</el-col>
</el-row>
<el-divider content-position="center">价格信息</el-divider>
<el-row :gutter="20">
<el-col :md="8" :sm="24">
<div class="price-info-item">
<div class="price-icon"></div>
<div class="price-title">每小时价格</div>
<div class="price-amount">${{ selectedInstance.hourly_price.toFixed(4) }}</div>
<div class="price-note">实例每小时费用</div>
</div>
</el-col>
<el-col :md="8" :sm="24">
<div class="price-info-item">
<div class="price-icon">💾</div>
<div class="price-title">存储价格</div>
<div class="price-amount">${{ (selectedInstance.disk_monthly_price / 30).toFixed(4) }}/</div>
<div class="price-note">{{ selectedInstance.disk_gb }}GB GP3卷每天费用</div>
</div>
</el-col>
<el-col :md="8" :sm="24">
<div class="price-info-item total">
<div class="price-icon">💰</div>
<div class="price-title">每月总费用</div>
<div class="price-amount">${{ selectedInstance.total_monthly_price.toFixed(2) }}</div>
<div class="price-note">预计30天使用总费用</div>
</div>
</el-col>
</el-row>
<div class="action-buttons">
<el-button type="success" icon="el-icon-shopping-cart-full" @click="addToComparison(selectedInstance)">添加到对比</el-button>
<el-button type="primary" icon="el-icon-s-finance" @click="goToCalculator">详细价格计算</el-button>
</div>
</el-card>
</div>
<!-- 价格对比部分 -->
<div v-if="comparisonList.length > 0" class="comparison-section animated fadeIn">
<h3><i class="el-icon-s-data"></i> AWS亚马逊报价单</h3>
<el-card class="comparison-card">
<!-- 报价单头部 -->
<div class="quote-header">
<div class="quote-title">AWS亚马逊报价单</div>
<div class="quote-info">
<table class="info-table">
<tr>
<td class="info-label">联系人:</td>
<td class="info-value">
<el-input v-model="quoteInfo.contact" placeholder="请输入联系人"></el-input>
</td>
<td></td>
<td class="info-label">签发日期:</td>
<td class="info-value">{{ getCurrentDate() }}</td>
</tr>
<tr>
<td class="info-label">电话:</td>
<td class="info-value">
<el-input v-model="quoteInfo.phone" placeholder="请输入电话"></el-input>
</td>
<td></td>
<td class="info-label">电话:</td>
<td class="info-value"></td>
</tr>
</table>
</div>
</div>
<!-- 报价单内容 -->
<el-table :data="comparisonList" border style="width: 100%" :stripe="true" class="comparison-table">
<el-table-column label="产品名称" width="80" align="center">
<template #default>
<span>EC2</span>
</template>
</el-table-column>
<el-table-column prop="instance_type" label="规格型号" width="150" align="center">
<template #default="scope">
<div>{{ formatSpecDescription(scope.row) }}</div>
</template>
</el-table-column>
<el-table-column label="磁盘" width="120" align="center">
<template #default="scope">
<span>{{ scope.row.disk_gb }}G GP3</span>
</template>
</el-table-column>
<el-table-column label="操作系统" width="120" align="center">
<template #default="scope">
<span>{{ formatOS(scope.row.operating_system) }}</span>
</template>
</el-table-column>
<el-table-column label="区域" width="120" align="center">
<template #default="scope">
<span>{{ getRegionName(scope.row.region) }}</span>
2025-03-26 12:48:43 +08:00
</template>
</el-table-column>
<el-table-column label="官方月付全额 美元USD" width="180" align="center">
<template #default="scope">
<span class="price-value highlight">${{ scope.row.total_monthly_price.toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="官方年付全额 美元USD" width="180" align="center">
<template #default="scope">
<span class="price-value highlight">${{ (scope.row.total_monthly_price * 12).toFixed(2) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center">
<template #default="scope">
<el-button
type="danger"
size="mini"
@click="removeFromComparison(scope.$index)"
icon="el-icon-delete">
移除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 说明事项 -->
<div class="quote-notes">
<div class="note-title">说明事项:</div>
<div class="note-items">
<div class="note-item">1. 以上价格仅包服务器和磁盘的费用, 以上价格仅包服务器和磁盘的费用, 公共带宽流量按官网价格 美国$0.12USD/GB</div>
</div>
</div>
<div class="quote-actions">
<div class="summary-item">
<div class="summary-label">对比实例数量:</div>
<div class="summary-value">{{ comparisonList.length }}</div>
</div>
<div class="summary-buttons">
<el-button type="primary" icon="el-icon-download" @click="exportComparison">导出报价单</el-button>
<el-button type="danger" icon="el-icon-delete" @click="clearComparison">清空报价单</el-button>
</div>
</div>
</el-card>
</div>
</el-card>
</div>
</template>
<script>
import apiService from '../api'
import XLSX from 'xlsx-js-style'
import { saveAs } from 'file-saver'
export default {
name: 'InstanceSearch',
data() {
return {
form: {
cpu_cores: null,
memory_gb: null,
disk_gb: 30, // 默认30GB
region: 'us-east-1',
disk_type: 'gp3',
operating_system: 'Linux' // 默认Linux
},
regions: [],
2025-04-02 21:51:36 +08:00
filteredRegions: [], // 添加筛选后的区域列表
2025-03-26 12:48:43 +08:00
searchResults: [],
selectedInstance: null,
comparisonList: [], // 用于存储要比较的实例
loading: false,
searched: false,
quoteInfo: {
contact: '林先生',
phone: '18626324958'
}
}
},
async created() {
try {
// 使用API服务获取区域列表
this.regions = await apiService.getRegions()
2025-04-02 21:51:36 +08:00
this.filteredRegions = [...this.regions] // 初始化筛选后的区域列表
2025-03-26 12:48:43 +08:00
// 如果数据加载成功,默认选择第一个区域
if (this.regions && this.regions.length > 0) {
this.form.region = this.regions[0].code
}
// 从本地存储加载之前保存的对比列表
const savedComparison = localStorage.getItem('instance_comparison')
if (savedComparison) {
this.comparisonList = JSON.parse(savedComparison)
}
} 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.name.toLowerCase().includes(query.toLowerCase()) ||
region.code.toLowerCase().includes(query.toLowerCase())
})
} else {
this.filteredRegions = [...this.regions]
}
},
2025-03-26 12:48:43 +08:00
async searchInstances() {
if (!this.form.cpu_cores && !this.form.memory_gb && !this.form.disk_gb) {
this.$message.warning('请至少指定一项配置要求')
return
}
this.loading = true
try {
// 使用API服务搜索实例
const data = await apiService.searchInstances({
cpu_cores: this.form.cpu_cores,
memory_gb: this.form.memory_gb,
disk_gb: this.form.disk_gb,
region: this.form.region,
operating_system: this.form.operating_system
})
// 为每个结果添加操作系统信息
this.searchResults = data.map(item => ({
...item,
operating_system: this.form.operating_system,
region: this.form.region
}))
this.searched = true
this.selectedInstance = null
if (this.searchResults.length === 0) {
this.$message.info('没有找到符合条件的实例')
}
} catch (error) {
console.error('Error searching instances:', error)
this.$message.error('搜索实例失败')
} finally {
this.loading = false
}
},
selectInstance(instance) {
this.selectedInstance = instance
// 平滑滚动到选定实例区域
setTimeout(() => {
const element = document.querySelector('.selected-instance')
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}, 100)
},
addToComparison(instance) {
if (!instance) return
// 检查是否已经添加过相同实例
const exists = this.comparisonList.find(
item => item.instance_type === instance.instance_type &&
item.region === this.form.region &&
item.operating_system === this.form.operating_system
)
if (exists) {
this.$message.warning('该实例已在对比列表中')
return
}
// 添加区域和操作系统信息到实例
const comparisonInstance = {
...instance,
region: this.form.region,
operating_system: this.form.operating_system
}
this.comparisonList.push(comparisonInstance)
this.$message.success('已添加到对比列表')
// 保存到本地存储
localStorage.setItem('instance_comparison', JSON.stringify(this.comparisonList))
// 滚动到对比列表区域
setTimeout(() => {
const element = document.querySelector('.comparison-section')
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}, 100)
},
removeFromComparison(index) {
this.comparisonList.splice(index, 1)
localStorage.setItem('instance_comparison', JSON.stringify(this.comparisonList))
this.$message.success('已从对比列表中移除')
},
clearComparison() {
this.comparisonList = []
localStorage.removeItem('instance_comparison')
this.$message.success('已清空对比列表')
},
exportComparison() {
if (this.comparisonList.length === 0) {
this.$message.warning('报价单为空')
return
}
// 创建工作簿
const wb = XLSX.utils.book_new()
// 创建标题行
const titleRow = [['AWS亚马逊报价单']]
// 创建联系人信息行
const infoRows = [
['联系人:', this.quoteInfo.contact, '', '签发日期:', this.getCurrentDate()],
['电话:', this.quoteInfo.phone, '', '电话:', '']
]
// 创建空行
const emptyRow = ['', '', '', '', '', '', '']
// 创建表头行
const headerRow = ['产品名称', '规格型号', '磁盘', '操作系统', '区域', '官方月付全额 美元USD', '官方年付全额 美元USD']
// 创建数据行
const dataRows = this.comparisonList.map(instance => [
'EC2',
`${instance.cpu}${instance.memory}G ${instance.instance_type}`,
`${instance.disk_gb}G GP3`,
this.formatOS(instance.operating_system),
this.getRegionName(instance.region),
2025-03-26 12:48:43 +08:00
instance.total_monthly_price.toFixed(2),
(instance.total_monthly_price * 12).toFixed(2)
])
// 创建说明事项行
const noteRows = [
emptyRow,
['说明事项:'],
['1. 以上价格仅包服务器和磁盘的费用, 以上价格仅包服务器和磁盘的费用, 公共带宽流量按官网价格 均价$0.12USD/GB']
]
// 合并所有行
const allRows = [...titleRow, ...infoRows, emptyRow, headerRow, ...dataRows, ...noteRows]
// 创建工作表
const ws = XLSX.utils.aoa_to_sheet(allRows)
// 设置列宽
const colWidths = [
{ wch: 15 }, // A列
{ wch: 25 }, // B列
{ wch: 15 }, // C列
{ wch: 15 }, // D列
{ wch: 25 }, // E列
2025-03-26 12:48:43 +08:00
{ wch: 25 }, // F列
{ wch: 25 } // G列
]
ws['!cols'] = colWidths
// 设置行高
const rowHeights = Array(allRows.length).fill({ hpt: 25 })
rowHeights[0] = { hpt: 35 } // 标题行
rowHeights[4] = { hpt: 30 } // 表头行
ws['!rows'] = rowHeights
// 设置单元格合并
const headerRowIndex = 4 // 表头行的索引
const noteStartRow = headerRowIndex + dataRows.length + 2 // 说明事项开始的行索引
ws['!merges'] = [
// 合并标题行 (A1:G1)
{ s: { r: 0, c: 0 }, e: { r: 0, c: 6 } },
// 合并说明事项行
{ s: { r: noteStartRow, c: 0 }, e: { r: noteStartRow, c: 6 } },
{ s: { r: noteStartRow + 1, c: 0 }, e: { r: noteStartRow + 1, c: 6 } },
{ s: { r: noteStartRow + 2, c: 0 }, e: { r: noteStartRow + 2, c: 6 } }
]
// 设置说明事项样式
for (let r = noteStartRow; r < noteStartRow + 3; r++) {
const cellRef = XLSX.utils.encode_cell({ r, c: 0 })
if (ws[cellRef]) {
ws[cellRef].s = {
...ws[cellRef].s,
fill: { patternType: 'solid', fgColor: { rgb: 'FFFBEA' } },
alignment: { horizontal: 'left', vertical: 'center' }
}
}
}
// 设置标题行样式
ws['A1'].s = {
font: { sz: 16, bold: true, color: { rgb: 'FFFFFF' } },
alignment: { horizontal: 'center', vertical: 'center' },
fill: { patternType: 'solid', fgColor: { rgb: '4472C4' } },
border: {
top: { style: 'thin' },
bottom: { style: 'thin' },
left: { style: 'thin' },
right: { style: 'thin' }
}
}
// 设置表头行样式
const cols = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
cols.forEach(col => {
const cellRef = `${col}${headerRowIndex + 1}`
if (!ws[cellRef]) ws[cellRef] = { v: '', t: 's' }
ws[cellRef].s = {
font: { bold: true },
alignment: { horizontal: 'center', vertical: 'center' },
fill: { patternType: 'solid', fgColor: { rgb: 'E0E0E0' } },
border: {
top: { style: 'thin' },
bottom: { style: 'thin' },
left: { style: 'thin' },
right: { style: 'thin' }
}
}
})
// 设置所有单元格的边框和对齐方式
for (let r = 0; r < allRows.length; r++) {
for (let c = 0; c < 7; c++) {
const cellRef = XLSX.utils.encode_cell({ r, c })
if (!ws[cellRef]) ws[cellRef] = { v: '', t: 's' }
if (!ws[cellRef].s) ws[cellRef].s = {}
ws[cellRef].s.border = {
top: { style: 'thin' },
bottom: { style: 'thin' },
left: { style: 'thin' },
right: { style: 'thin' }
}
ws[cellRef].s.alignment = { horizontal: 'center', vertical: 'center' }
}
}
// 将工作表添加到工作簿
XLSX.utils.book_append_sheet(wb, ws, 'AWS报价单')
// 导出工作簿
const wbout = XLSX.write(wb, { bookType: 'xlsx', type: 'array', bookSST: true, cellStyles: true })
const blob = new Blob([wbout], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
saveAs(blob, 'AWS亚马逊报价单.xlsx')
this.$message.success('报价单已导出为Excel文件')
},
goToCalculator() {
if (!this.selectedInstance) return
// 创建计算器请求参数
const params = {
instance_type: this.selectedInstance.instance_type,
region: this.form.region,
disk_gb: this.form.disk_gb,
operating_system: this.form.operating_system
}
// 保存到本地存储并跳转到计算器页面
localStorage.setItem('calculator_params', JSON.stringify(params))
this.$router.push('/')
},
// 获取区域的中文名称
getRegionName(regionCode) {
const region = this.regions.find(r => r.code === regionCode)
return region ? region.name : regionCode
},
// 获取当前日期 格式YYYY/MM/DD
getCurrentDate() {
const date = new Date()
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
return `${year}/${month}/${day}`
},
// 格式化规格描述
formatSpecDescription(instance) {
return `${instance.cpu}${instance.memory}G ${instance.instance_type}`
},
// 格式化操作系统显示
formatOS(os) {
if (os === 'Linux') return 'Linux'
if (os === 'Windows') return 'Windows'
return os
}
}
}
</script>
<style scoped>
.instance-search {
max-width: 1200px;
margin: 0 auto;
position: relative;
z-index: 1;
}
.search-card {
margin-bottom: 20px;
transition: all 0.3s ease;
}
.search-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%;
}
.flexible-col {
display: flex;
align-items: center;
}
.search-button {
width: 100%;
height: 40px;
font-size: 16px;
transition: all 0.3s ease;
}
.search-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3);
}
.loading-container {
padding: 20px 0;
}
.search-results {
margin-top: 30px;
}
.search-results h3 {
margin-bottom: 20px;
color: #2c3e50;
font-size: 18px;
text-align: center;
}
.search-results h3 i {
margin-right: 8px;
color: var(--secondary-color);
}
.result-table {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.05);
}
.instance-type {
display: flex;
justify-content: center;
}
.spec-value {
display: flex;
justify-content: center;
align-items: center;
font-weight: 600;
}
.spec-value i {
margin-right: 5px;
color: #409EFF;
}
.price-breakdown {
display: flex;
flex-direction: column;
gap: 5px;
padding: 5px;
}
.price-item {
display: flex;
justify-content: space-between;
font-size: 12px;
}
.price-item.total {
margin-top: 5px;
padding-top: 5px;
border-top: 1px dashed #ddd;
font-weight: 600;
}
.price-label {
color: #7f8c8d;
}
.price-value {
font-weight: 500;
}
.price-value.highlight {
color: #2ecc71;
font-weight: 700;
}
.no-results {
padding: 40px 0;
}
.selected-instance {
margin-top: 40px;
padding-top: 20px;
border-top: 1px dashed #e0e0e0;
}
.selected-instance h3 {
margin-bottom: 20px;
color: #2c3e50;
font-size: 18px;
text-align: center;
}
.selected-instance h3 i {
margin-right: 8px;
color: var(--accent-color);
}
.instance-card {
border-left: 4px solid var(--accent-color);
}
.instance-info-item {
text-align: center;
padding: 15px;
height: 100%;
}
.info-label {
color: #7f8c8d;
font-size: 14px;
margin-bottom: 8px;
}
.info-value {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
}
.price-info-item {
text-align: center;
padding: 20px 15px;
border-radius: 8px;
background-color: #f8f9fa;
margin-bottom: 15px;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
transition: all 0.3s ease;
}
.price-info-item:hover {
transform: translateY(-5px);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.05);
}
.price-info-item.total {
background-color: #e8f7f0;
}
.price-icon {
font-size: 24px;
margin-bottom: 10px;
}
.price-title {
color: #7f8c8d;
font-size: 14px;
margin-bottom: 10px;
}
.price-amount {
font-size: 22px;
font-weight: 700;
color: #2c3e50;
margin-bottom: 5px;
}
.price-note {
font-size: 12px;
color: #95a5a6;
}
.action-buttons {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.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) {
.action-buttons {
flex-direction: column;
}
.instance-info-item,
.price-info-item {
margin-bottom: 15px;
}
}
.action-buttons-cell {
display: flex;
flex-direction: column;
gap: 5px;
}
.comparison-section {
margin-top: 40px;
padding-top: 20px;
border-top: 1px dashed #e0e0e0;
}
.comparison-section h3 {
margin-bottom: 20px;
color: #2c3e50;
font-size: 18px;
text-align: center;
}
.comparison-card {
margin-bottom: 20px;
position: relative;
}
.comparison-table {
margin-bottom: 15px;
}
.quote-header {
margin-bottom: 20px;
}
.quote-title {
background-color: #00bcd4;
color: white;
text-align: center;
padding: 10px;
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.quote-info {
border: 1px solid #e0e0e0;
}
.info-table {
width: 100%;
border-collapse: collapse;
}
.info-table td {
padding: 8px;
border: 1px solid #e0e0e0;
}
.info-label {
width: 80px;
font-weight: bold;
background-color: #f5f5f5;
}
.info-value {
width: 200px;
}
.quote-notes {
margin-top: 15px;
border: 1px solid #ffcc00;
background-color: #fffbea;
padding: 10px;
}
.note-title {
font-weight: bold;
margin-bottom: 10px;
}
.note-items {
color: #333;
}
.note-item {
margin-bottom: 5px;
}
.quote-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
}
</style>