From 2376623df6c432d732f20063bbe5641362412d73 Mon Sep 17 00:00:00 2001 From: wangqifan Date: Fri, 29 Aug 2025 19:11:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=89=8D=E7=AB=AF=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=EF=BC=8C=E8=B0=83=E6=95=B4=E8=A1=A8=E5=8D=95=E5=B8=83?= =?UTF-8?q?=E5=B1=80=E5=92=8C=E6=A0=B7=E5=BC=8F=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=BC=8F=E8=AE=BE=E8=AE=A1=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=A7=BB=E5=8A=A8=E7=AB=AF=E5=8D=A1=E7=89=87=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=EF=BC=8C=E4=BF=AE=E6=94=B9AWS=E6=8A=A5=E4=BB=B7?= =?UTF-8?q?=E5=8D=95=E7=9A=84=E6=98=BE=E7=A4=BA=E6=96=B9=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.vue | 2 +- frontend/src/views/InstanceSearch.vue | 782 ++++++++++++++++---------- 2 files changed, 486 insertions(+), 298 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 00fdfe0..5ae7b4c 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -7,7 +7,7 @@ AWS报价 - AWS折扣 + diff --git a/frontend/src/views/InstanceSearch.vue b/frontend/src/views/InstanceSearch.vue index 8fa3f31..d2bf6e3 100644 --- a/frontend/src/views/InstanceSearch.vue +++ b/frontend/src/views/InstanceSearch.vue @@ -9,9 +9,9 @@
- + - + - + - + - + - + @@ -81,7 +81,7 @@ - + @@ -89,8 +89,8 @@ - - + + 搜索实例 @@ -105,81 +105,124 @@

符合条件的实例

- - - - - - - - - - - - - - - - - - - - - - - - - - - +
+
+ + 查看详情 + + + 加入对比 + +
+ +
@@ -191,37 +234,37 @@ - +
实例类型
{{ selectedInstance.instance_type }}
- +
CPU核心
{{ selectedInstance.cpu }} 核
- +
内存
{{ selectedInstance.memory }} GB
- +
磁盘
{{ selectedInstance.disk_gb }} GB (GP3)
- +
区域
{{ getRegionName(form.region) }}
- +
操作系统
{{ form.operating_system }}
@@ -232,7 +275,7 @@ 价格信息 - +
⏱️
每小时价格
@@ -240,7 +283,7 @@
实例每小时费用
- +
💾
存储价格
@@ -248,7 +291,7 @@
{{ selectedInstance.disk_gb }}GB GP3卷每天费用
- +
💰
每月总费用
@@ -274,91 +317,135 @@
AWS亚马逊报价单
- - - - - - - - - - - - - - - -
联系人: - - 签发日期:{{ getCurrentDate() }}
电话: - - 电话:
+
+
+ 联系人: + +
+
+ 签发日期: + {{ getCurrentDate() }} +
+
+
+
+ 电话: + +
+
+ 电话: + +
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ +
说明事项:
-
1. 以上价格仅包服务器和磁盘的费用, 以上价格仅包服务器和磁盘的费用, 公共带宽流量按官网价格 美国$0.12USD/GB
+
1. 以上价格仅包服务器和磁盘的费用, 公共带宽流量按官网价格 美国$0.12USD/GB
@@ -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; } - \ No newline at end of file + +.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; + } +} + + +