更新前端页面,调整表单布局和样式,优化响应式设计,添加移动端卡片视图,修改AWS报价单的显示方式。
This commit is contained in:
parent
02ba9ea77f
commit
2376623df6
@ -7,7 +7,7 @@
|
||||
<el-menu mode="horizontal" router :default-active="'/awsSearch'" class="menu" background-color="#3498db" text-color="#fff" active-text-color="#ffd04b">
|
||||
<!-- <el-menu-item index="/"><i class="el-icon-s-finance"></i> 价格计算器</el-menu-item> -->
|
||||
<el-menu-item index="/awsSearch"><i class="el-icon-search"></i>AWS报价</el-menu-item>
|
||||
<el-menu-item index="/awsSearchDiscount"><i class="el-icon-search"></i>AWS折扣</el-menu-item>
|
||||
<!-- <el-menu-item index="/awsSearchDiscount"><i class="el-icon-search"></i>AWS折扣</el-menu-item> -->
|
||||
<!-- <el-menu-item index="/compare"><i class="el-icon-data-analysis"></i> 价格对比</el-menu-item> -->
|
||||
</el-menu>
|
||||
</div>
|
||||
|
||||
@ -9,9 +9,9 @@
|
||||
</template>
|
||||
|
||||
<div class="form-container">
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-form :model="form" label-width="120px" label-position="top">
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="8" :sm="24">
|
||||
<el-col :lg="8" :md="12" :sm="24">
|
||||
<el-form-item label="CPU 核心数">
|
||||
<el-input-number
|
||||
v-model="form.cpu_cores"
|
||||
@ -24,7 +24,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="8" :sm="24">
|
||||
<el-col :lg="8" :md="12" :sm="24">
|
||||
<el-form-item label="内存(GB)">
|
||||
<el-input-number
|
||||
v-model="form.memory_gb"
|
||||
@ -37,7 +37,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="8" :sm="24">
|
||||
<el-col :lg="8" :md="12" :sm="24">
|
||||
<el-form-item label="磁盘(GB)">
|
||||
<el-input-number
|
||||
v-model="form.disk_gb"
|
||||
@ -52,7 +52,7 @@
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="6" :sm="24">
|
||||
<el-col :lg="6" :md="12" :sm="24">
|
||||
<el-form-item label="区域">
|
||||
<el-select
|
||||
v-model="form.region"
|
||||
@ -72,7 +72,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="6" :sm="24">
|
||||
<el-col :lg="6" :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"></el-option>
|
||||
@ -81,7 +81,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="6" :sm="24">
|
||||
<el-col :lg="6" :md="12" :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>
|
||||
@ -89,8 +89,8 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :md="6" :sm="24" class="flexible-col">
|
||||
<el-form-item>
|
||||
<el-col :lg="6" :md="12" :sm="24" class="flexible-col">
|
||||
<el-form-item label=" " class="action-form-item">
|
||||
<el-button type="primary" @click="searchInstances" icon="el-icon-search" class="search-button">搜索实例</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@ -105,17 +105,17 @@
|
||||
<div v-else-if="searchResults.length" class="search-results animated fadeIn">
|
||||
<h3><i class="el-icon-s-data"></i> 符合条件的实例</h3>
|
||||
|
||||
<!-- Desktop View: Table -->
|
||||
<div v-if="!isMobile" class="table-responsive">
|
||||
<el-table :data="searchResults" border style="width: 100%" :stripe="true" class="result-table">
|
||||
<el-table-column prop="instance_type" label="实例类型" width="120">
|
||||
<el-table-column prop="instance_type" label="实例类型" width="120" fixed>
|
||||
<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">
|
||||
@ -123,7 +123,6 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="memory" label="内存(GB)" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<div class="spec-value">
|
||||
@ -131,7 +130,6 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="disk_gb" label="磁盘(GB)" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<div class="spec-value">
|
||||
@ -139,7 +137,6 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="每月价格" width="260">
|
||||
<template #default="scope">
|
||||
<div class="price-breakdown">
|
||||
@ -158,8 +155,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="200" align="center">
|
||||
<el-table-column label="操作" width="200" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<div class="action-buttons-cell">
|
||||
<el-button
|
||||
@ -182,6 +178,53 @@
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- Mobile View: Cards -->
|
||||
<div v-else class="mobile-results-list">
|
||||
<el-card v-for="item in searchResults" :key="item.instance_type" class="result-card">
|
||||
<div class="card-header-mobile">
|
||||
<el-tag type="info" size="medium">{{ item.instance_type }}</el-tag>
|
||||
<div class="total-price">${{ item.total_monthly_price.toFixed(2) }}<span class="price-unit">/月</span></div>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p class="description">{{ item.description }}</p>
|
||||
<div class="specs-list">
|
||||
<div class="spec-item">
|
||||
<i class="el-icon-cpu"></i>
|
||||
<strong>CPU:</strong>
|
||||
<span>{{ item.cpu }} 核心</span>
|
||||
</div>
|
||||
<div class="spec-item">
|
||||
<i class="el-icon-coin"></i>
|
||||
<strong>内存:</strong>
|
||||
<span>{{ item.memory }} GB</span>
|
||||
</div>
|
||||
<div class="spec-item">
|
||||
<i class="el-icon-folder"></i>
|
||||
<strong>磁盘:</strong>
|
||||
<span>{{ item.disk_gb }} GB</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="selectInstance(item)"
|
||||
icon="el-icon-select">
|
||||
查看详情
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="addToComparison(item)"
|
||||
icon="el-icon-plus">
|
||||
加入对比
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="searched" class="no-results">
|
||||
<el-empty description="没有找到符合条件的实例,请尝试降低配置要求"></el-empty>
|
||||
</div>
|
||||
@ -191,37 +234,37 @@
|
||||
|
||||
<el-card class="instance-card">
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="4" :sm="24">
|
||||
<el-col :lg="4" :md="8" :sm="12" :xs="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">
|
||||
<el-col :lg="4" :md="8" :sm="12" :xs="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">
|
||||
<el-col :lg="4" :md="8" :sm="12" :xs="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">
|
||||
<el-col :lg="4" :md="8" :sm="12" :xs="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">
|
||||
<el-col :lg="4" :md="8" :sm="12" :xs="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">
|
||||
<el-col :lg="4" :md="8" :sm="12" :xs="24">
|
||||
<div class="instance-info-item">
|
||||
<div class="info-label">操作系统</div>
|
||||
<div class="info-value">{{ form.operating_system }}</div>
|
||||
@ -232,7 +275,7 @@
|
||||
<el-divider content-position="center">价格信息</el-divider>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :md="8" :sm="24">
|
||||
<el-col :lg="8" :md="24">
|
||||
<div class="price-info-item">
|
||||
<div class="price-icon">⏱️</div>
|
||||
<div class="price-title">每小时价格</div>
|
||||
@ -240,7 +283,7 @@
|
||||
<div class="price-note">实例每小时费用</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :md="8" :sm="24">
|
||||
<el-col :lg="8" :md="24">
|
||||
<div class="price-info-item">
|
||||
<div class="price-icon">💾</div>
|
||||
<div class="price-title">存储价格</div>
|
||||
@ -248,7 +291,7 @@
|
||||
<div class="price-note">{{ selectedInstance.disk_gb }}GB GP3卷每天费用</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :md="8" :sm="24">
|
||||
<el-col :lg="8" :md="24">
|
||||
<div class="price-info-item total">
|
||||
<div class="price-icon">💰</div>
|
||||
<div class="price-title">每月总费用</div>
|
||||
@ -274,32 +317,33 @@
|
||||
<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 class="info-row">
|
||||
<div class="info-item">
|
||||
<span class="info-label">联系人:</span>
|
||||
<el-input v-model="quoteInfo.contact" placeholder="请输入联系人" size="small"></el-input>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">签发日期:</span>
|
||||
<span class="info-value">{{ getCurrentDate() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-item">
|
||||
<span class="info-label">电话:</span>
|
||||
<el-input v-model="quoteInfo.phone" placeholder="请输入电话" size="small"></el-input>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">电话:</span>
|
||||
<span class="info-value"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 报价单内容 -->
|
||||
<div class="table-responsive" v-if="!isMobile">
|
||||
<el-table :data="comparisonList" border style="width: 100%" :stripe="true" class="comparison-table">
|
||||
<el-table-column label="产品名称" width="80" align="center">
|
||||
<el-table-column label="产品名称" width="80" align="center" fixed>
|
||||
<template #default>
|
||||
<span>EC2</span>
|
||||
</template>
|
||||
@ -341,7 +385,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<el-table-column label="操作" width="80" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="danger"
|
||||
@ -353,12 +397,55 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- Mobile: 卡片列表 -->
|
||||
<div v-else class="mobile-comparison-list">
|
||||
<el-card v-for="(item, idx) in comparisonList" :key="item.instance_type + '-' + idx" class="mobile-comparison-card">
|
||||
<div class="mobile-comparison-header">
|
||||
<div class="mobile-comparison-title">EC2</div>
|
||||
<div class="mobile-comparison-price">${{ item.total_monthly_price.toFixed(2) }}<span class="price-unit">/月</span></div>
|
||||
</div>
|
||||
<div class="mobile-comparison-body">
|
||||
<div class="mobile-row">
|
||||
<span class="mobile-label">规格</span>
|
||||
<span class="mobile-value">{{ formatSpecDescription(item) }}</span>
|
||||
</div>
|
||||
<div class="mobile-row">
|
||||
<span class="mobile-label">磁盘</span>
|
||||
<span class="mobile-value">{{ item.disk_gb }}G GP3</span>
|
||||
</div>
|
||||
<div class="mobile-row">
|
||||
<span class="mobile-label">系统</span>
|
||||
<span class="mobile-value">{{ formatOS(item.operating_system) }}</span>
|
||||
</div>
|
||||
<div class="mobile-row">
|
||||
<span class="mobile-label">区域</span>
|
||||
<span class="mobile-value">{{ getRegionName(item.region) }}</span>
|
||||
</div>
|
||||
<div class="mobile-row">
|
||||
<span class="mobile-label">年付</span>
|
||||
<span class="mobile-value">${{ (item.total_monthly_price * 12).toFixed(2) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-comparison-actions">
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="removeFromComparison(idx)"
|
||||
icon="el-icon-delete"
|
||||
plain>
|
||||
移除
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 说明事项 -->
|
||||
<div class="quote-notes">
|
||||
<div class="note-title">说明事项:</div>
|
||||
<div class="note-items">
|
||||
<div class="note-item">1. 以上价格仅包服务器和磁盘的费用, 以上价格仅包服务器和磁盘的费用, 公共带宽流量按官网价格 美国$0.12USD/GB</div>
|
||||
<div class="note-item">1. 以上价格仅包服务器和磁盘的费用, 公共带宽流量按官网价格 美国$0.12USD/GB</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -396,30 +483,35 @@ export default {
|
||||
operating_system: 'Linux' // 默认Linux
|
||||
},
|
||||
regions: [],
|
||||
filteredRegions: [], // 添加筛选后的区域列表
|
||||
filteredRegions: [],
|
||||
searchResults: [],
|
||||
selectedInstance: null,
|
||||
comparisonList: [], // 用于存储要比较的实例
|
||||
comparisonList: [],
|
||||
loading: false,
|
||||
searched: false,
|
||||
quoteInfo: {
|
||||
contact: '林先生',
|
||||
phone: '18626324958'
|
||||
},
|
||||
isMobile: false
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.checkScreenSize();
|
||||
window.addEventListener('resize', this.checkScreenSize);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.removeEventListener('resize', this.checkScreenSize);
|
||||
},
|
||||
async created() {
|
||||
try {
|
||||
// 使用API服务获取区域列表
|
||||
this.regions = await apiService.getRegions()
|
||||
this.filteredRegions = [...this.regions] // 初始化筛选后的区域列表
|
||||
this.filteredRegions = [...this.regions]
|
||||
|
||||
// 如果数据加载成功,默认选择第一个区域
|
||||
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)
|
||||
@ -430,6 +522,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkScreenSize() {
|
||||
this.isMobile = window.innerWidth < 768;
|
||||
},
|
||||
filterRegions(query) {
|
||||
if (query) {
|
||||
this.filteredRegions = this.regions.filter(region => {
|
||||
@ -448,7 +543,6 @@ export default {
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
// 使用API服务搜索实例
|
||||
const data = await apiService.searchInstances({
|
||||
cpu_cores: this.form.cpu_cores,
|
||||
memory_gb: this.form.memory_gb,
|
||||
@ -457,7 +551,6 @@ export default {
|
||||
operating_system: this.form.operating_system
|
||||
})
|
||||
|
||||
// 为每个结果添加操作系统信息
|
||||
this.searchResults = data.map(item => ({
|
||||
...item,
|
||||
operating_system: this.form.operating_system,
|
||||
@ -479,7 +572,6 @@ export default {
|
||||
},
|
||||
selectInstance(instance) {
|
||||
this.selectedInstance = instance
|
||||
// 平滑滚动到选定实例区域
|
||||
setTimeout(() => {
|
||||
const element = document.querySelector('.selected-instance')
|
||||
if (element) {
|
||||
@ -490,7 +582,6 @@ export default {
|
||||
addToComparison(instance) {
|
||||
if (!instance) return
|
||||
|
||||
// 检查是否已经添加过相同实例
|
||||
const exists = this.comparisonList.find(
|
||||
item => item.instance_type === instance.instance_type &&
|
||||
item.region === this.form.region &&
|
||||
@ -502,7 +593,6 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
// 添加区域和操作系统信息到实例
|
||||
const comparisonInstance = {
|
||||
...instance,
|
||||
region: this.form.region,
|
||||
@ -512,10 +602,8 @@ export default {
|
||||
this.comparisonList.push(comparisonInstance)
|
||||
this.$message.success('已添加到对比列表')
|
||||
|
||||
// 保存到本地存储
|
||||
localStorage.setItem('instance_comparison', JSON.stringify(this.comparisonList))
|
||||
|
||||
// 滚动到对比列表区域
|
||||
setTimeout(() => {
|
||||
const element = document.querySelector('.comparison-section')
|
||||
if (element) {
|
||||
@ -539,25 +627,14 @@ export default {
|
||||
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}`,
|
||||
@ -567,52 +644,34 @@ export default {
|
||||
instance.total_monthly_price.toFixed(2),
|
||||
(instance.total_monthly_price * 12).toFixed(2)
|
||||
])
|
||||
|
||||
// 创建说明事项行
|
||||
const noteRows = [
|
||||
emptyRow,
|
||||
['说明事项:'],
|
||||
['1. 以上价格仅包服务器和磁盘的费用, 以上价格仅包服务器和磁盘的费用, 公共带宽流量按官网价格 均价$0.12USD/GB']
|
||||
['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列
|
||||
{ wch: 25 }, // F列
|
||||
{ wch: 25 } // G列
|
||||
{ wch: 15 }, { wch: 25 }, { wch: 15 }, { wch: 15 }, { wch: 25 }, { wch: 25 }, { wch: 25 }
|
||||
]
|
||||
ws['!cols'] = colWidths
|
||||
|
||||
// 设置行高
|
||||
const rowHeights = Array(allRows.length).fill({ hpt: 25 })
|
||||
rowHeights[0] = { hpt: 35 } // 标题行
|
||||
rowHeights[4] = { hpt: 30 } // 表头行
|
||||
rowHeights[0] = { hpt: 35 }
|
||||
rowHeights[4] = { hpt: 30 }
|
||||
ws['!rows'] = rowHeights
|
||||
|
||||
// 设置单元格合并
|
||||
const headerRowIndex = 4 // 表头行的索引
|
||||
const noteStartRow = headerRowIndex + dataRows.length + 2 // 说明事项开始的行索引
|
||||
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]) {
|
||||
@ -624,20 +683,13 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置标题行样式
|
||||
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' }
|
||||
}
|
||||
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}`
|
||||
@ -646,35 +698,22 @@ export default {
|
||||
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' }
|
||||
}
|
||||
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.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')
|
||||
@ -684,7 +723,6 @@ export default {
|
||||
goToCalculator() {
|
||||
if (!this.selectedInstance) return
|
||||
|
||||
// 创建计算器请求参数
|
||||
const params = {
|
||||
instance_type: this.selectedInstance.instance_type,
|
||||
region: this.form.region,
|
||||
@ -692,16 +730,13 @@ export default {
|
||||
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()
|
||||
@ -709,11 +744,9 @@ export default {
|
||||
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'
|
||||
@ -727,8 +760,7 @@ export default {
|
||||
.instance-search {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
@ -736,10 +768,6 @@ export default {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.search-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -771,19 +799,13 @@ export default {
|
||||
|
||||
.flexible-col {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.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 {
|
||||
@ -801,15 +823,14 @@ export default {
|
||||
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);
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.instance-type {
|
||||
@ -879,13 +900,8 @@ export default {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.selected-instance h3 i {
|
||||
margin-right: 8px;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.instance-card {
|
||||
border-left: 4px solid var(--accent-color);
|
||||
border-left: 4px solid var(--accent-color, #409EFF);
|
||||
}
|
||||
|
||||
.instance-info-item {
|
||||
@ -956,6 +972,7 @@ export default {
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.animated {
|
||||
@ -978,21 +995,11 @@ export default {
|
||||
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;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.comparison-section {
|
||||
@ -1010,7 +1017,6 @@ export default {
|
||||
|
||||
.comparison-card {
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.comparison-table {
|
||||
@ -1033,26 +1039,29 @@ export default {
|
||||
|
||||
.quote-info {
|
||||
border: 1px solid #e0e0e0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.info-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
.info-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-table td {
|
||||
padding: 8px;
|
||||
border: 1px solid #e0e0e0;
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1 1 250px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
width: 80px;
|
||||
font-weight: bold;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
width: 200px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.quote-notes {
|
||||
@ -1060,6 +1069,7 @@ export default {
|
||||
border: 1px solid #ffcc00;
|
||||
background-color: #fffbea;
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.note-title {
|
||||
@ -1067,18 +1077,196 @@ export default {
|
||||
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;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.summary-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Mobile Card Styles */
|
||||
.mobile-results-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
.result-card {
|
||||
border-left: 4px solid #409EFF;
|
||||
}
|
||||
.result-card .el-card__body {
|
||||
padding: 0;
|
||||
}
|
||||
.card-header-mobile {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
.total-price {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #e67e22;
|
||||
}
|
||||
.price-unit {
|
||||
font-size: 14px;
|
||||
color: #7f8c8d;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.card-content {
|
||||
padding: 15px;
|
||||
}
|
||||
.description {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.specs-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.spec-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.spec-item i {
|
||||
color: #409EFF;
|
||||
font-size: 18px;
|
||||
}
|
||||
.spec-item strong {
|
||||
width: 50px;
|
||||
}
|
||||
.card-actions {
|
||||
background-color: #fcfcfc;
|
||||
border-top: 1px solid #EBEEF5;
|
||||
padding: 10px 15px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
.card-actions .el-button {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
@media (max-width: 992px) {
|
||||
.el-form--label-top .el-form-item__label {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.action-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.instance-search {
|
||||
padding: 10px;
|
||||
}
|
||||
.card-header span {
|
||||
font-size: 18px;
|
||||
}
|
||||
.form-container {
|
||||
padding: 10px 0;
|
||||
}
|
||||
.flexible-col {
|
||||
align-items: stretch;
|
||||
}
|
||||
.search-button {
|
||||
margin-top: 0;
|
||||
}
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
.action-buttons .el-button {
|
||||
width: 100%;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
.instance-info-item,
|
||||
.price-info-item {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.quote-actions {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.summary-buttons {
|
||||
justify-content: center;
|
||||
}
|
||||
.summary-buttons .el-button {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
/* Mobile comparison card styles */
|
||||
.mobile-comparison-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.mobile-comparison-card {
|
||||
border-left: 4px solid #00bcd4;
|
||||
}
|
||||
.mobile-comparison-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
.mobile-comparison-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
.mobile-comparison-price {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #e67e22;
|
||||
}
|
||||
.mobile-comparison-body {
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.mobile-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
.mobile-label {
|
||||
color: #7f8c8d;
|
||||
min-width: 44px;
|
||||
}
|
||||
.mobile-value {
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
}
|
||||
.mobile-comparison-actions {
|
||||
padding: 10px 16px 14px 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.mobile-comparison-list { display: none; }
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user