714 lines
16 KiB
Vue
714 lines
16 KiB
Vue
<template>
|
||
<div class="create-instance-page">
|
||
<main class="create-main">
|
||
<div class="breadcrumb-row">
|
||
<div class="breadcrumb">
|
||
<span class="link" @click="$router.push('/homePage/computeMarket')"> <i class="iconfont icon-icon_arrow_left"></i> 返回算力市场</span>
|
||
</div>
|
||
</div>
|
||
|
||
<section class="content-card">
|
||
<div class="card-header">
|
||
<h2>计费方式:</h2>
|
||
<button type="button" class="link-btn">计费规则</button>
|
||
</div>
|
||
<div class="option-list">
|
||
<button
|
||
v-for="option in billingOptions"
|
||
:key="option.value"
|
||
type="button"
|
||
class="option-btn"
|
||
:class="{ active: billingType === option.value }"
|
||
@click="billingType = option.value"
|
||
>
|
||
{{ option.label }}
|
||
</button>
|
||
</div>
|
||
<p class="card-tip">创建完主机后仍然可以转换计费方式。如选择按量计费,价格发生变动以实例开机时的价格为准</p>
|
||
</section>
|
||
|
||
<section class="content-card">
|
||
<div class="card-header">
|
||
<h2>选择主机:</h2>
|
||
</div>
|
||
|
||
<div class="host-table-wrap">
|
||
<table class="host-table">
|
||
<thead>
|
||
<tr>
|
||
<th></th>
|
||
<th>主机ID</th>
|
||
<th>算力型号/显存</th>
|
||
<th>空闲GPU</th>
|
||
<th>每GPU分配</th>
|
||
<th>CPU型号</th>
|
||
<th>硬盘</th>
|
||
<th>驱动/CUDA</th>
|
||
<th>价格(单卡)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr
|
||
v-for="host in hosts"
|
||
:key="host.id"
|
||
:class="{ selected: selectedHostId === host.id }"
|
||
@click="selectedHostId = host.id"
|
||
>
|
||
<td>
|
||
<span class="radio-dot"></span>
|
||
</td>
|
||
<td class="host-name">{{ host.name }}</td>
|
||
<td>
|
||
<strong class="gpu-name">{{ host.gpu }}</strong>
|
||
<span class="gpu-memory"> {{ host.memory }}</span>
|
||
</td>
|
||
<td><strong class="free-count">{{ host.freeGpu }}</strong></td>
|
||
<td>{{ host.allocation }}</td>
|
||
<td class="muted small">{{ host.cpu }}</td>
|
||
<td class="muted small">{{ host.disk }}</td>
|
||
<td class="muted small">{{ host.driver }}</td>
|
||
<td>
|
||
<strong class="price">¥{{ host.price }}/时</strong>
|
||
<span class="origin-price">¥{{ host.originalPrice }}/时</span>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="field-block">
|
||
<h3>GPU数量:</h3>
|
||
<div class="option-list option-list--compact">
|
||
<button
|
||
v-for="count in gpuCounts"
|
||
:key="count"
|
||
type="button"
|
||
class="option-btn gpu-count-btn"
|
||
:class="{ active: gpuCount === count }"
|
||
@click="gpuCount = count"
|
||
>
|
||
{{ count }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="disk-row">
|
||
<span>数据盘: 免费50GB</span>
|
||
<label>
|
||
<input v-model="needExpandDisk" type="checkbox">
|
||
<span>需要扩容</span>
|
||
</label>
|
||
<div v-if="needExpandDisk" class="disk-input">
|
||
<input v-model.number="expandDiskSize" type="number" min="50">
|
||
<span>GB</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="spec-bar">
|
||
<strong>实例规格:</strong>
|
||
<span>{{ specSummary }}</span>
|
||
</section>
|
||
|
||
<section class="content-card">
|
||
<div class="card-header">
|
||
<h2>镜像:</h2>
|
||
<button type="button" class="link-btn">没有我要的环境?</button>
|
||
</div>
|
||
<div class="option-list">
|
||
<button
|
||
v-for="option in imageTypes"
|
||
:key="option.value"
|
||
type="button"
|
||
class="option-btn image-option"
|
||
:class="{ active: imageType === option.value }"
|
||
@click="imageType = option.value"
|
||
>
|
||
{{ option.label }}
|
||
<em v-if="option.hot">HOT</em>
|
||
</button>
|
||
</div>
|
||
<p class="card-tip">基础镜像包含常用基本软件,如:深度学习框架、Miniconda等。如需其他软件可创建后安装</p>
|
||
<div class="select-wrap">
|
||
<select v-model="selectedEnv">
|
||
<option value="">请选择框架名称/框架版本/Python版本/CUDA版本</option>
|
||
<option value="pytorch">PyTorch 2.1.0 / Python 3.10 / CUDA 12.1</option>
|
||
<option value="tensorflow">TensorFlow 2.14.0 / Python 3.10 / CUDA 12.1</option>
|
||
<option value="paddle">PaddlePaddle 2.5.1 / Python 3.10 / CUDA 11.8</option>
|
||
</select>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="content-card">
|
||
<div class="card-header">
|
||
<h2>优惠券:</h2>
|
||
</div>
|
||
<div class="select-wrap">
|
||
<select v-model="coupon">
|
||
<option value="">请选择</option>
|
||
<option value="10">新人专享满100减10元</option>
|
||
<option value="50">充值满500减50元</option>
|
||
<option value="100">VIP用户满1000减100元</option>
|
||
</select>
|
||
</div>
|
||
</section>
|
||
|
||
<aside class="submit-bar">
|
||
<div class="submit-bar__inner">
|
||
<div class="submit-costs">
|
||
<span>日常费用: ¥0.00/日</span>
|
||
<span>
|
||
配置费用
|
||
<strong>¥{{ estimatedPrice }}/时</strong>
|
||
</span>
|
||
<button type="button" class="detail-btn">费用明细</button>
|
||
</div>
|
||
<div class="submit-actions">
|
||
<!-- <span class="balance">账户余额 ¥0.00 <em>余额不足去充值</em></span> -->
|
||
<button type="button" class="cancel-btn" @click="$router.push('/homePage/computeMarket')">取消</button>
|
||
<button type="button" class="create-btn" @click="submitCreate">
|
||
<i class="iconfont icon-icon_guanji-kaiji"></i>
|
||
创建并开机
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
</main>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { mapState } from 'vuex'
|
||
|
||
export default {
|
||
name: 'ComputeMarketCreateInstance',
|
||
data() {
|
||
return {
|
||
billingType: 'hourly',
|
||
selectedHostId: 'D42',
|
||
gpuCount: 1,
|
||
needExpandDisk: false,
|
||
expandDiskSize: 100,
|
||
imageType: 'basic',
|
||
selectedEnv: '',
|
||
coupon: '',
|
||
billingOptions: [
|
||
{ label: '按量计费', value: 'hourly' },
|
||
{ label: '包日', value: 'daily' },
|
||
{ label: '包周', value: 'weekly' },
|
||
{ label: '包月', value: 'monthly' },
|
||
{ label: '包年', value: 'yearly' }
|
||
],
|
||
gpuCounts: [1, 2, 3, 4, 5, 6, 7, 8],
|
||
imageTypes: [
|
||
{ label: '基础镜像', value: 'basic' },
|
||
{ label: '社区镜像', value: 'community', hot: true },
|
||
{ label: '我的镜像', value: 'custom' }
|
||
],
|
||
hosts: [
|
||
{
|
||
id: 'D42',
|
||
name: 'D42机',
|
||
gpu: 'RTX PRO 6000',
|
||
memory: '96GB',
|
||
freeGpu: '1 / 8',
|
||
allocation: 'CPU: 25核 内存: 120GB',
|
||
cpu: 'Xeon(R) Platinum 8470Q',
|
||
disk: '数据盘: 50GB 可扩容: 1622GB',
|
||
driver: '驱动: 595.58.03 CUDA: 13.2',
|
||
price: 5.98,
|
||
originalPrice: 7.97
|
||
}
|
||
]
|
||
}
|
||
},
|
||
computed: {
|
||
...mapState({
|
||
loginStateVuex: state => state.login.loginState
|
||
}),
|
||
loginState() {
|
||
const userId = sessionStorage.getItem('userId')
|
||
return this.loginStateVuex || (userId !== null && userId !== 'null' && userId !== '')
|
||
},
|
||
selectedHost() {
|
||
return this.hosts.find(host => host.id === this.selectedHostId) || this.hosts[0]
|
||
},
|
||
dataDiskText() {
|
||
return this.needExpandDisk ? `数据盘:${this.expandDiskSize}GB SSD` : '数据盘:免费50GB SSD'
|
||
},
|
||
specSummary() {
|
||
const host = this.selectedHost
|
||
return `GPU型号:${host.gpu} *${this.gpuCount}卡 | CPU:25核心 | 内存:120GB | 系统盘:30GB | ${this.dataDiskText}`
|
||
},
|
||
estimatedPrice() {
|
||
return (Number(this.selectedHost.price) * this.gpuCount).toFixed(2)
|
||
}
|
||
},
|
||
created() {
|
||
const gpu = this.$route.query.gpu
|
||
if (gpu) {
|
||
this.hosts[0].gpu = String(gpu).split('/')[0].trim()
|
||
this.hosts[0].memory = (String(gpu).split('/')[1] || '96 GB').trim()
|
||
}
|
||
},
|
||
methods: {
|
||
submitCreate() {
|
||
if (!this.loginState) {
|
||
this.$message.warning('请先登录后再创建实例')
|
||
window.dispatchEvent(new Event('kboss-open-login-dialog'))
|
||
return
|
||
}
|
||
|
||
if (!this.selectedEnv) {
|
||
this.$message.warning('请先选择镜像环境')
|
||
return
|
||
}
|
||
this.$message.success('实例创建请求已提交')
|
||
this.$router.push('/homePage/computeMarket')
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.create-instance-page {
|
||
min-height: 100vh;
|
||
padding: 82px 0 88px;
|
||
background:
|
||
radial-gradient(circle at top left, rgba(30, 111, 255, 0.12), transparent 30%),
|
||
linear-gradient(180deg, #f7fbff 0%, #f5f7fb 100%);
|
||
}
|
||
|
||
.create-main {
|
||
width: min(1160px, calc(100% - 48px));
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.breadcrumb-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
.breadcrumb {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
color: #98a2b3;
|
||
font-size: 14px;
|
||
|
||
.link {
|
||
color: #1e6fff;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
}
|
||
|
||
strong {
|
||
color: #344054;
|
||
}
|
||
}
|
||
|
||
.content-card,
|
||
.spec-bar,
|
||
.submit-bar {
|
||
background: rgba(255, 255, 255, 0.94);
|
||
border: 1px solid rgba(219, 234, 254, 0.78);
|
||
border-radius: 18px;
|
||
box-shadow: 0 12px 34px rgba(31, 72, 135, 0.08);
|
||
}
|
||
aside{
|
||
margin: 0;
|
||
}
|
||
.content-card {
|
||
padding: 24px;
|
||
margin-bottom: 18px;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16px;
|
||
|
||
h2 {
|
||
margin: 0;
|
||
color: #1f2937;
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
.link-btn {
|
||
color: #1e6fff;
|
||
font-size: 14px;
|
||
background: transparent;
|
||
border: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.option-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
.option-list--compact {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.option-btn {
|
||
min-width: 88px;
|
||
height: 40px;
|
||
padding: 0 18px;
|
||
color: #5f6b7a;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
background: #fff;
|
||
border: 2px solid #e3e8f0;
|
||
border-radius: 12px;
|
||
cursor: pointer;
|
||
position: relative;
|
||
transition: all 0.2s ease;
|
||
|
||
&.active,
|
||
&:hover {
|
||
color: #1e6fff;
|
||
border-color: #1e6fff;
|
||
background: #eef5ff;
|
||
}
|
||
}
|
||
|
||
.image-option em {
|
||
position: absolute;
|
||
top: -10px;
|
||
right: -10px;
|
||
padding: 2px 7px;
|
||
color: #fff;
|
||
font-size: 10px;
|
||
font-style: normal;
|
||
background: #ef4444;
|
||
border-radius: 999px;
|
||
}
|
||
|
||
.card-tip {
|
||
margin: 0;
|
||
color: #8a94a6;
|
||
font-size: 13px;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.host-table-wrap {
|
||
overflow-x: auto;
|
||
margin-bottom: 22px;
|
||
border: 1px solid #edf1f7;
|
||
border-radius: 16px;
|
||
}
|
||
|
||
.host-table {
|
||
width: 100%;
|
||
min-width: 1060px;
|
||
border-collapse: collapse;
|
||
font-size: 13px;
|
||
|
||
th {
|
||
padding: 14px 12px;
|
||
color: #667085;
|
||
text-align: left;
|
||
font-weight: 700;
|
||
background: #f8fafc;
|
||
}
|
||
|
||
td {
|
||
padding: 16px 12px;
|
||
color: #5f6b7a;
|
||
border-top: 1px solid #edf1f7;
|
||
}
|
||
|
||
tr {
|
||
cursor: pointer;
|
||
}
|
||
|
||
tr.selected {
|
||
background: #f2f7ff;
|
||
}
|
||
}
|
||
|
||
.radio-dot {
|
||
display: inline-block;
|
||
width: 16px;
|
||
height: 16px;
|
||
border: 2px solid #1e6fff;
|
||
border-radius: 50%;
|
||
box-shadow: inset 0 0 0 4px #fff;
|
||
background: #1e6fff;
|
||
}
|
||
|
||
.host-name {
|
||
color: #1f2937;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.gpu-name {
|
||
color: #1e6fff;
|
||
}
|
||
|
||
.gpu-memory,
|
||
.muted {
|
||
color: #8a94a6;
|
||
}
|
||
|
||
.small {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.free-count {
|
||
color: #10b981;
|
||
}
|
||
|
||
.price {
|
||
display: block;
|
||
color: #ef4444;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.origin-price {
|
||
display: block;
|
||
color: #98a2b3;
|
||
font-size: 12px;
|
||
text-decoration: line-through;
|
||
}
|
||
|
||
.field-block {
|
||
margin-bottom: 18px;
|
||
|
||
h3 {
|
||
margin: 0 0 12px;
|
||
color: #1f2937;
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
.gpu-count-btn {
|
||
min-width: 44px;
|
||
padding: 0 14px;
|
||
}
|
||
|
||
.disk-row {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 18px;
|
||
color: #5f6b7a;
|
||
font-size: 14px;
|
||
|
||
label {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
input[type='checkbox'] {
|
||
width: 15px;
|
||
height: 15px;
|
||
accent-color: #1e6fff;
|
||
}
|
||
}
|
||
|
||
.disk-input {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
|
||
input {
|
||
width: 92px;
|
||
height: 34px;
|
||
padding: 0 10px;
|
||
border: 1px solid #dfe5ef;
|
||
border-radius: 10px;
|
||
outline: none;
|
||
}
|
||
}
|
||
|
||
.spec-bar {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
padding: 18px 20px;
|
||
margin-bottom: 18px;
|
||
background: #eef5ff;
|
||
|
||
strong {
|
||
flex-shrink: 0;
|
||
color: #1e6fff;
|
||
font-size: 16px;
|
||
}
|
||
|
||
span {
|
||
color: #344054;
|
||
font-size: 14px;
|
||
line-height: 1.7;
|
||
}
|
||
}
|
||
|
||
.select-wrap {
|
||
position: relative;
|
||
|
||
select {
|
||
width: 100%;
|
||
height: 46px;
|
||
padding: 0 14px;
|
||
color: #667085;
|
||
background: #fff;
|
||
border: 1px solid #dfe5ef;
|
||
border-radius: 12px;
|
||
outline: none;
|
||
}
|
||
}
|
||
|
||
.submit-bar {
|
||
position: fixed;
|
||
right: 0;
|
||
bottom: 0 !important;
|
||
left: 0;
|
||
z-index: 30;
|
||
width: 100%;
|
||
padding: 0;
|
||
background: rgba(255, 255, 255, 0.98);
|
||
border: none;
|
||
border-top: 1px solid rgba(223, 229, 239, 0.95);
|
||
border-radius: 0;
|
||
box-shadow: 0 -10px 30px rgba(31, 72, 135, 0.08);
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.submit-bar__inner {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 24px;
|
||
width: min(1600px, calc(100% - 48px));
|
||
min-height: 86px;
|
||
margin: 0 auto;
|
||
padding: 18px 0;
|
||
}
|
||
|
||
.submit-costs,
|
||
.submit-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.submit-costs span,
|
||
.balance {
|
||
color: #667085;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.submit-costs strong {
|
||
color: #ef4444;
|
||
font-size: 20px;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.detail-btn {
|
||
color: #1e6fff;
|
||
font-size: 13px;
|
||
background: transparent;
|
||
border: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.balance em {
|
||
margin-left: 6px;
|
||
color: #ef4444;
|
||
font-size: 12px;
|
||
font-style: normal;
|
||
}
|
||
|
||
.cancel-btn,
|
||
.create-btn {
|
||
height: 48px;
|
||
padding: 0 26px;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.cancel-btn {
|
||
color: #667085;
|
||
background: #fff;
|
||
border: 1px solid #dfe5ef;
|
||
|
||
&:hover {
|
||
color: #1e6fff;
|
||
border-color: #1e6fff;
|
||
}
|
||
}
|
||
|
||
.create-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: #fff;
|
||
background: linear-gradient(135deg, #1e6fff, #43a3ff);
|
||
border: none;
|
||
box-shadow: 0 10px 20px rgba(30, 111, 255, 0.24);
|
||
|
||
&:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 12px 24px rgba(30, 111, 255, 0.3);
|
||
}
|
||
|
||
i {
|
||
font-size: 15px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.create-main {
|
||
width: calc(100% - 24px);
|
||
}
|
||
|
||
.content-card {
|
||
padding: 18px;
|
||
}
|
||
|
||
.card-header,
|
||
.spec-bar {
|
||
align-items: flex-start;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.submit-bar__inner {
|
||
width: calc(100% - 24px);
|
||
min-height: auto;
|
||
align-items: stretch;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
padding: 16px 0;
|
||
}
|
||
|
||
.submit-costs,
|
||
.submit-actions {
|
||
justify-content: space-between;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
|
||
.create-btn {
|
||
flex: 1;
|
||
justify-content: center;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.create-instance-page {
|
||
padding-top: 16px;
|
||
}
|
||
}
|
||
</style>
|