Compare commits
No commits in common. "2d0d6eb59b63faa81669e4028218f4bd6b1cc36e" and "6ec2e38d1365b3350acb46a1260fc5ee789cc920" have entirely different histories.
2d0d6eb59b
...
6ec2e38d13
2
.env.dev
2
.env.dev
@ -2,5 +2,5 @@
|
|||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
# api
|
# api
|
||||||
VITE_API_BASIC="http://10.10.1.30:8888"
|
VITE_API_BASIC="http://10.10.1.33:8888"
|
||||||
|
|
||||||
|
|||||||
@ -32,23 +32,7 @@ export const tixian=(params:any)=>request.put("/v1/balance/withdraw",params)
|
|||||||
//充值记录
|
//充值记录
|
||||||
export const getUpList=(params:any)=>request.get("/v1/balance/top_up_list",{params})
|
export const getUpList=(params:any)=>request.get("/v1/balance/top_up_list",{params})
|
||||||
|
|
||||||
//充值
|
|
||||||
export const postTopUp=(params:any)=>request.get("/v1/balance/top_up",{params})
|
|
||||||
|
|
||||||
//提现记录
|
//提现记录
|
||||||
export const getWithdrawList=(params:any)=>request.get("/v1/balance/withdraw_list",{params})
|
export const getWithdrawList=(params:any)=>request.get("/v1/balance/withdraw_list",{params})
|
||||||
|
|
||||||
//获取镜像
|
|
||||||
export const getImages=(params:any)=>request.get('/v1/image/images',{params})
|
|
||||||
|
|
||||||
//获取总费用
|
|
||||||
export const getTotalCost=(params:any)=>request.get('/v1/host/host_amount',{params})
|
|
||||||
//获取服务器信息
|
|
||||||
export const getHostInfo=(params:any)=>request.get('/v1/host/info',{params})
|
|
||||||
|
|
||||||
//兑换算力点
|
|
||||||
export const exchangePoint=(params:any)=>request.put('/v1/balance/exchange_point',params)
|
|
||||||
|
|
||||||
//创建实例
|
|
||||||
export const createHost=(params:any)=>request.post('/v1/host/host_case',params)
|
|
||||||
|
|
||||||
|
|||||||
@ -183,7 +183,7 @@ import {
|
|||||||
} from '@ant-design/icons-vue'
|
} from '@ant-design/icons-vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { getUpList,postTopUp } from "@/apis/home"
|
import { getUpList } from "@/apis/home"
|
||||||
|
|
||||||
// 用户余额
|
// 用户余额
|
||||||
const balance = ref(0)
|
const balance = ref(0)
|
||||||
@ -327,16 +327,7 @@ const handleRecharge = () => {
|
|||||||
// 确认充值
|
// 确认充值
|
||||||
const handleRechargeConfirm = () => {
|
const handleRechargeConfirm = () => {
|
||||||
modalLoading.value = true
|
modalLoading.value = true
|
||||||
try {
|
|
||||||
const params={
|
|
||||||
amount:selectedRechargeAmount.value,
|
|
||||||
payMethod:selectedPaymentMethod.value,
|
|
||||||
payThirdNumber:'11111'
|
|
||||||
}
|
|
||||||
// const res=await postTopUp()
|
|
||||||
} catch (error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
balance.value += selectedRechargeAmount.value
|
balance.value += selectedRechargeAmount.value
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
|||||||
@ -223,11 +223,6 @@ const dateFormat = 'YYYY-MM-DD'
|
|||||||
|
|
||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const userInfoStr=localStorage.getItem("userInfo")
|
|
||||||
if(userInfoStr){
|
|
||||||
const userInfo = JSON.parse(userInfoStr);
|
|
||||||
accountInfo.availableBalance=userInfo.balance
|
|
||||||
}
|
|
||||||
fetchWithdrawalRecords()
|
fetchWithdrawalRecords()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
</a-breadcrumb>
|
</a-breadcrumb>
|
||||||
|
|
||||||
<!-- 余额卡片 -->
|
<!-- 余额卡片 -->
|
||||||
<a-card class="balance-card" >
|
<a-card class="balance-card" :bordered="false">
|
||||||
<div class="balance-info">
|
<div class="balance-info">
|
||||||
<div class="balance-item">
|
<div class="balance-item">
|
||||||
<div class="balance-label">
|
<div class="balance-label">
|
||||||
@ -426,8 +426,8 @@ onMounted(() => {
|
|||||||
/* 样式保持不变,同上 */
|
/* 样式保持不变,同上 */
|
||||||
.points-exchange-page {
|
.points-exchange-page {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background-color: #ffffff;
|
background-color: #f0f2f5;
|
||||||
/* min-height: 100vh; */
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb {
|
.breadcrumb {
|
||||||
@ -437,7 +437,7 @@ onMounted(() => {
|
|||||||
.balance-card {
|
.balance-card {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); */
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-info {
|
.balance-info {
|
||||||
@ -481,7 +481,7 @@ onMounted(() => {
|
|||||||
.exchange-card {
|
.exchange-card {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); */
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.exchange-content {
|
.exchange-content {
|
||||||
@ -609,7 +609,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.history-card {
|
.history-card {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); */
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.agreement-content {
|
.agreement-content {
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
<a-card title="算力点" class="card computing-card">
|
<a-card title="算力点" class="card computing-card">
|
||||||
<div class="computing-content">
|
<div class="computing-content">
|
||||||
<div class="computing-amount">
|
<div class="computing-amount">
|
||||||
<span class="amount-value">{{formatAmount(userInfo.computingPowerPoint || 0)
|
<span class="amount-value">{{ '¥' + formatAmount(userInfo.computingPowerPoint || 0)
|
||||||
}}</span>
|
}}</span>
|
||||||
<!-- <span class="amount-unit">点</span> -->
|
<!-- <span class="amount-unit">点</span> -->
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -56,6 +56,7 @@
|
|||||||
:columns="columns"
|
:columns="columns"
|
||||||
bordered
|
bordered
|
||||||
:pagination="paginationState"
|
:pagination="paginationState"
|
||||||
|
:rowSelection="rowSelection"
|
||||||
@change="onTableChange"
|
@change="onTableChange"
|
||||||
:rowKey="record => record.id || record.order_number"
|
:rowKey="record => record.id || record.order_number"
|
||||||
>
|
>
|
||||||
@ -90,7 +91,9 @@
|
|||||||
</template>
|
</template>
|
||||||
<!-- 支付状态列 -->
|
<!-- 支付状态列 -->
|
||||||
<template v-if="column.key === 'pay_status'">
|
<template v-if="column.key === 'pay_status'">
|
||||||
<a-tag> {{ record.pay_status }}</a-tag>
|
<a-tag :color="getPayStatusColor(record.pay_status)">
|
||||||
|
{{ getPayStatusText(record.pay_status) }}
|
||||||
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<!-- <template v-if="column.key === 'action'">
|
<!-- <template v-if="column.key === 'action'">
|
||||||
<a-space>
|
<a-space>
|
||||||
|
|||||||
@ -37,7 +37,7 @@ const columns = [
|
|||||||
width: 200,
|
width: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '算力点',
|
title: '金额',
|
||||||
dataIndex: 'amount',
|
dataIndex: 'amount',
|
||||||
key: 'amount',
|
key: 'amount',
|
||||||
width: 200,
|
width: 200,
|
||||||
|
|||||||
@ -3,17 +3,20 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2 class="page-title">文件存储</h2>
|
<h2 class="page-title">文件存储</h2>
|
||||||
<a-button type="primary" @click="showModal" class="create-btn">
|
<a-button type="primary" @click="showModal" class="create-btn">
|
||||||
<template #icon>
|
<template #icon><PlusOutlined /></template>
|
||||||
<PlusOutlined />
|
|
||||||
</template>
|
|
||||||
新建存储
|
新建存储
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 表格 -->
|
<!-- 表格 -->
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<a-table :columns="columns" :data-source="storageList" bordered :row-key="record => record.id"
|
<a-table
|
||||||
:pagination="{ pageSize: 8 }" class="storage-table">
|
:columns="columns"
|
||||||
|
:data-source="storageList"
|
||||||
|
:row-key="record => record.id"
|
||||||
|
:pagination="{ pageSize: 8 }"
|
||||||
|
class="storage-table"
|
||||||
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'size'">
|
<template v-if="column.key === 'size'">
|
||||||
<span class="data-cell">{{ record.size }} GB</span>
|
<span class="data-cell">{{ record.size }} GB</span>
|
||||||
@ -35,24 +38,57 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 创建弹窗 -->
|
<!-- 创建弹窗 -->
|
||||||
<a-modal v-model:open="modalVisible" title="创建存储空间" @ok="handleCreate" @cancel="modalVisible = false" :width="520"
|
<a-modal
|
||||||
class="create-modal">
|
v-model:open="modalVisible"
|
||||||
<a-form :model="createForm" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }" ref="createFormRef"
|
title="创建存储空间"
|
||||||
class="create-form">
|
@ok="handleCreate"
|
||||||
|
@cancel="modalVisible = false"
|
||||||
|
:width="520"
|
||||||
|
class="create-modal"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
:model="createForm"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:wrapper-col="{ span: 18 }"
|
||||||
|
ref="createFormRef"
|
||||||
|
class="create-form"
|
||||||
|
>
|
||||||
<a-form-item label="存储名称" name="name" :rules="[{ required: true, message: '请输入存储名称' }]">
|
<a-form-item label="存储名称" name="name" :rules="[{ required: true, message: '请输入存储名称' }]">
|
||||||
<a-input v-model:value="createForm.name" placeholder="请输入存储空间名称" size="large" />
|
<a-input
|
||||||
|
v-model:value="createForm.name"
|
||||||
|
placeholder="请输入存储空间名称"
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="存储大小" name="size"
|
<a-form-item
|
||||||
:rules="[{ required: true, message: '请输入存储大小' }, { type: 'number', min: 1, message: '至少1GB' }]">
|
label="存储大小"
|
||||||
<a-input-number v-model:value="createForm.size" :min="1" :max="10000" style="width: 100%" size="large"
|
name="size"
|
||||||
addon-after="GB" />
|
:rules="[{ required: true, message: '请输入存储大小' }, { type: 'number', min: 1, message: '至少1GB' }]"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="createForm.size"
|
||||||
|
:min="1"
|
||||||
|
:max="10000"
|
||||||
|
style="width: 100%"
|
||||||
|
size="large"
|
||||||
|
addon-after="GB"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="购买时长" name="duration"
|
<a-form-item
|
||||||
:rules="[{ required: true, message: '请输入购买时长' }, { type: 'number', min: 1, message: '至少1天' }]">
|
label="购买时长"
|
||||||
<a-input-number v-model:value="createForm.duration" :min="1" :max="365" style="width: 100%" size="large"
|
name="duration"
|
||||||
addon-after="天" />
|
:rules="[{ required: true, message: '请输入购买时长' }, { type: 'number', min: 1, message: '至少1天' }]"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="createForm.duration"
|
||||||
|
:min="1"
|
||||||
|
:max="365"
|
||||||
|
style="width: 100%"
|
||||||
|
size="large"
|
||||||
|
addon-after="天"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<div class="cost-summary">
|
<div class="cost-summary">
|
||||||
@ -170,7 +206,8 @@ const handleCreate = async () => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.file-storage-page {
|
.file-storage-page {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
background: #ffffff;
|
background: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
@ -198,7 +235,7 @@ const handleCreate = async () => {
|
|||||||
.table-container {
|
.table-container {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03); */
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
<div class="header-bottom">
|
<div class="header-bottom">
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<a-button type="primary" @click="handleRent" class="action-btn">新建实例</a-button>
|
<a-button type="primary" @click="handleRent" class="action-btn">新建实例</a-button>
|
||||||
<!-- <a-button @click="handleBatchRenew" class="action-btn">批量续费</a-button> -->
|
<a-button @click="handleBatchRenew" class="action-btn">批量续费</a-button>
|
||||||
<a-button class="refresh-btn" @click="refreshData">
|
<a-button class="refresh-btn" @click="refreshData">
|
||||||
<reload-outlined />
|
<reload-outlined />
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|||||||
@ -5,30 +5,26 @@
|
|||||||
<a-card class="card select-server" title="服务器选择">
|
<a-card class="card select-server" title="服务器选择">
|
||||||
<div class="list-filter">
|
<div class="list-filter">
|
||||||
<div class="filter-item">
|
<div class="filter-item">
|
||||||
<span class="filter-label">实例名称:</span>
|
<span class="filter-label">计费方式:</span>
|
||||||
<div class="filter-content">
|
<div class="filter-content">
|
||||||
<a-input v-model:value="instanceName"></a-input>
|
<a-radio-group v-model:value="billingType" button-style="solid"
|
||||||
</div>
|
@change="handleBillingTypeChange">
|
||||||
</div>
|
<a-radio-button value="payg">按量计费</a-radio-button>
|
||||||
<div class="filter-item">
|
<a-radio-button value="daily">包日</a-radio-button>
|
||||||
<span class="filter-label">计费方式<span style="color: #ff4d4f;margin: 0 2px">*</span>:</span>
|
<a-radio-button value="weekly">包周</a-radio-button>
|
||||||
<div class="filter-content">
|
<a-radio-button value="monthly">包月</a-radio-button>
|
||||||
<a-radio-group v-model:value="billingType" button-style="solid">
|
<a-radio-button value="year">包年</a-radio-button>
|
||||||
<a-radio-button value="PayOnTime">按量计费</a-radio-button>
|
|
||||||
<a-radio-button value="PayOnDay">包日</a-radio-button>
|
|
||||||
<a-radio-button value="PayOnWeek">包周</a-radio-button>
|
|
||||||
<a-radio-button value="PayOnMonth">包月</a-radio-button>
|
|
||||||
<a-radio-button value="PayOnYear">包年</a-radio-button>
|
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- GPU数量 -->
|
<!-- GPU数量 -->
|
||||||
<div class="filter-item">
|
<div class="filter-item">
|
||||||
<span class="filter-label">GPU数量<span style="color: #ff4d4f;margin: 0 2px">*</span><span>:</span></span>
|
<span class="filter-label">GPU数量:</span>
|
||||||
<div class="filter-content">
|
<div class="filter-content">
|
||||||
<a-radio-group v-model:value="gpuCount" button-style="solid">
|
<a-radio-group v-model:value="gpuCount" button-style="solid" @change="handleGpuCountChange">
|
||||||
<a-radio-button v-for="count in gpuCountOptions" :key="count" :value="count">
|
<a-radio-button v-for="count in gpuCountOptions" :key="count" :value="count"
|
||||||
|
:disabled="isGpuCountDisabled(count)">
|
||||||
{{ count }}
|
{{ count }}
|
||||||
</a-radio-button>
|
</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
@ -36,25 +32,25 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-item">
|
<div class="filter-item">
|
||||||
<span class="filter-label">镜像<span style="color: #ff4d4f;margin: 0 2px;">*</span>:</span>
|
<span class="filter-label">镜像:</span>
|
||||||
<a-form layout="vertical">
|
<a-form layout="vertical">
|
||||||
<a-form-item class="image-type">
|
<a-form-item class="image-type">
|
||||||
<a-radio-group v-model:value="imageType" button-style="solid"
|
<a-radio-group v-model:value="imageType" button-style="solid">
|
||||||
@change="getServiceImages()">
|
<a-radio-button value="platformImage">基础镜像</a-radio-button>
|
||||||
<a-radio-button value="SYSTEM">基础镜像</a-radio-button>
|
|
||||||
<a-radio-button value="USER">我的镜像</a-radio-button>
|
<a-radio-button value="customImage">我的镜像</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item>
|
<a-form-item v-if="imageType === 'platformImage'">
|
||||||
<div class="basic-image" style="margin-top: 15px;">
|
<div class="basic-image" style="margin-top: 15px;">
|
||||||
<a-select ref="select" v-model:value="selectedImage" style="width: 300px"
|
|
||||||
@change="handleChange">
|
<a-cascader v-model:value="selectedImage" :options="imageOptions"
|
||||||
<a-select-option v-for="item in imageOptions" :value="item.id">{{ item.label
|
placeholder="请选择框架名称/框架版本/Python版本/CUDA版本" style="width: 400px"
|
||||||
}}</a-select-option>
|
:show-search="{ filter }" />
|
||||||
</a-select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
@ -62,43 +58,120 @@
|
|||||||
|
|
||||||
<!-- 主机表格 -->
|
<!-- 主机表格 -->
|
||||||
<div class="machine-table">
|
<div class="machine-table">
|
||||||
<a-descriptions layout="vertical" bordered :column="8">
|
<div class="table-header">
|
||||||
|
<span class="label">选择主机:</span>
|
||||||
|
<span class="total-count">共 {{ filteredMachineList.length }} 台可用主机</span>
|
||||||
|
</div>
|
||||||
|
<a-table :columns="columns" :data-source="filteredMachineList" :pagination="false"
|
||||||
|
:row-selection="{ type: 'radio', selectedRowKeys: [selectedMachineId] }"
|
||||||
|
@row-click="handleRowClick" :row-class-name="getRowClassName" :loading="tableLoading"
|
||||||
|
size="middle">
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action'">
|
||||||
|
<a-radio :checked="selectedMachineId === record.id"
|
||||||
|
@change="() => handleSelectMachine(record.id)" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'gpuModel'">
|
||||||
|
<div class="gpu-info">
|
||||||
|
<div class="gpu-name">{{ record.gpuModel }}</div>
|
||||||
|
<div class="gpu-memory">{{ record.gpuMemory }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'gpuInfo'">
|
||||||
|
<div class="gpu-availability">
|
||||||
|
<span class="free-count">{{ record.freeGpu }}</span>
|
||||||
|
<span class="separator">/</span>
|
||||||
|
<span class="total-count">{{ record.totalGpu }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'cpuInfo'">
|
||||||
|
<div class="cpu-memory-info">
|
||||||
|
<div>CPU:{{ record.cpuCores }}核</div>
|
||||||
|
<div>内存:{{ record.memory }}GB</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'disk'">
|
||||||
|
<div class="disk-info">
|
||||||
|
<div>数据盘:{{ record.dataDisk }}GB</div>
|
||||||
|
<div>可扩容:{{ record.expandableDisk }}GB</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'driver'">
|
||||||
|
<div class="driver-info">
|
||||||
|
<div>驱动:{{ record.driver }}</div>
|
||||||
|
<div>CUDA:{{ record.cuda }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'price'">
|
||||||
|
<div class="price-info">
|
||||||
|
<div class="current-price">
|
||||||
|
¥{{ getPrice(record) }}/{{ billingUnit }}
|
||||||
|
</div>
|
||||||
|
<div v-if="record.originalPrice" class="original-price">
|
||||||
|
¥{{ getOriginalPrice(record) }}/{{ billingUnit }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
|
||||||
<a-descriptions-item label="主机ID">{{ serviceInfo.id }}</a-descriptions-item>
|
<!-- 空状态 -->
|
||||||
<a-descriptions-item label="算力型号/显存">{{ serviceInfo.gpuType + '/' + serviceInfo.vram + 'GB'
|
<div v-if="filteredMachineList.length === 0" class="empty-state">
|
||||||
}}</a-descriptions-item>
|
<a-empty description="暂无可用主机,请调整筛选条件" />
|
||||||
<a-descriptions-item label="空闲GPU">{{ serviceInfo.gpuAvailableNum }}</a-descriptions-item>
|
</div>
|
||||||
<a-descriptions-item label="每GPU分配">
|
|
||||||
<span>{{ 'CPU:' + serviceInfo.cpuNum + '核' }}</span><br>
|
|
||||||
<span>{{ '内存:' + serviceInfo.memory + "GB" }}</span>
|
|
||||||
</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="CPU型号">{{ serviceInfo.cpuType }}</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="硬盘">
|
|
||||||
<span>{{ '数据盘:' + serviceInfo.dataDisk + 'GB' }}</span><br>
|
|
||||||
<span>{{ '系统盘:' + serviceInfo.systemDisk + "GB" }}</span>
|
|
||||||
</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="驱动/CUDA">{{ serviceInfo.gpuDriver + '/' + serviceInfo.cudaVersion
|
|
||||||
}}</a-descriptions-item>
|
|
||||||
<a-descriptions-item label="消耗算力点(单卡)">{{ serviceInfo.price }}</a-descriptions-item>
|
|
||||||
</a-descriptions>
|
|
||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
|
|
||||||
<!-- 优惠券 -->
|
<!-- 优惠券 -->
|
||||||
<a-card class="card coupon-selection">
|
<a-card class="card coupon-selection" title="算力券">
|
||||||
<a-checkbox v-model:checked="checked">是否使用算力券<span style="color:#f34646;">{{ ' ( 可用点数:'+couponTotal+' 点 )' }}</span></a-checkbox>
|
<div class="coupon-content">
|
||||||
|
<a-select v-model:value="selectedCoupon" placeholder="请选择算力券" style="width: 200px" allow-clear>
|
||||||
|
<a-select-option value="">不使用算力券</a-select-option>
|
||||||
|
<a-select-option v-for="coupon in availableCoupons" :key="coupon.id" :value="coupon.id">
|
||||||
|
{{ coupon.name }} - ¥{{ coupon.amount }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<div class="coupon-balance">
|
||||||
|
可用算力券:{{ availableCoupons.length }} 张
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
<!-- 底部操作栏 -->
|
<!-- 底部操作栏 -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-actions">
|
<div class="footer-actions">
|
||||||
|
|
||||||
<div class="price-summary">
|
<div class="price-summary">
|
||||||
<div>消耗算力点:{{totalMoney }}</div>
|
|
||||||
<!-- 账户余额信息 -->
|
<!-- 账户余额信息 -->
|
||||||
<div class="account-info" style="display: flex;align-items: center;">
|
<div class="account-info">
|
||||||
<div class="balance-label" style="margin-right: 10px;">可用算力点:{{ userInfo.computingPowerPoint+' 点' }}</div>
|
<div class="balance-label">账户余额</div>
|
||||||
<!-- <div v-if="isShow" style="color: #ff4d4f;font-size: 12px;cursor: pointer;"
|
<div class="balance-amount">
|
||||||
@click="router.push('/layout/admin/balance')">
|
<span class="amount">¥11</span>
|
||||||
算力点不足去充值</div> -->
|
<a href="#" class="recharge-link">算力点不足去充值</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 费用明细 -->
|
||||||
|
<div class="price-details">
|
||||||
|
<div class="price-item">
|
||||||
|
<span class="price-label">日常费用:</span>
|
||||||
|
<span class="price-value">¥{{ totalPrice }}</span>
|
||||||
|
<span class="price-unit">/{{ billingUnit }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="price-item">
|
||||||
|
<span class="price-label">配置费用:</span>
|
||||||
|
<span class="price-value">¥{{ totalPrice }}</span>
|
||||||
|
<span class="price-unit">/{{ billingUnit }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 原价显示(如果有优惠) -->
|
||||||
|
<div v-if="originalTotalPrice" class="original-price-item">
|
||||||
|
<span class="original-price-label">原价:</span>
|
||||||
|
<span class="original-price-value">¥{{ originalTotalPrice }}/{{ billingUnit }}</span>
|
||||||
|
<span class="discount-badge">-{{ calculateDiscount() }}%</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -115,12 +188,14 @@
|
|||||||
取消
|
取消
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" size="large" @click="handleCreate" :loading="creating"
|
<a-button type="primary" size="large" @click="handleCreate" :loading="creating"
|
||||||
:disabled="selectedImage==''" class="create-btn">
|
:disabled="!canCreate" class="create-btn">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<PlusOutlined />
|
<PlusOutlined />
|
||||||
</template>
|
</template>
|
||||||
立即创建
|
立即创建
|
||||||
|
<span v-if="totalPrice !== '0.00'" class="create-price">
|
||||||
|
¥{{ totalPrice }}/{{ billingUnit }}
|
||||||
|
</span>
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -129,13 +204,32 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, reactive, watch, onMounted } from 'vue'
|
import { ref, computed, reactive, watch } from 'vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import type { TableColumnsType } from 'ant-design-vue'
|
import type { TableColumnsType } from 'ant-design-vue'
|
||||||
import { useRoute,useRouter } from 'vue-router'
|
|
||||||
import { getImages } from "@/apis/home"
|
// 类型定义
|
||||||
import { getMyCouponTotal, getTotalCost, getHostInfo,createHost } from "@/apis/home"
|
interface Machine {
|
||||||
const router=useRouter()
|
id: string
|
||||||
|
machineName: string
|
||||||
|
gpuModel: string
|
||||||
|
gpuMemory: string
|
||||||
|
freeGpu: number
|
||||||
|
totalGpu: number
|
||||||
|
cpuCores: number
|
||||||
|
memory: number
|
||||||
|
cpuModel: string
|
||||||
|
dataDisk: number
|
||||||
|
expandableDisk: number
|
||||||
|
driver: string
|
||||||
|
cuda: string
|
||||||
|
basePrice: number
|
||||||
|
originalPrice?: number
|
||||||
|
region: string
|
||||||
|
zone: string
|
||||||
|
availableGpuCount: number
|
||||||
|
}
|
||||||
|
|
||||||
interface GpuModelOption {
|
interface GpuModelOption {
|
||||||
value: string
|
value: string
|
||||||
label: string
|
label: string
|
||||||
@ -144,101 +238,459 @@ interface GpuModelOption {
|
|||||||
regions: string[]
|
regions: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const instanceName=ref('')
|
interface RegionOption {
|
||||||
const checked = ref(false)
|
value: string
|
||||||
const totalMoney = ref(0)
|
label: string
|
||||||
|
tag?: { color: string; text: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ZoneOption {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
regions: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Coupon {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
amount: number
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const billingType = ref('PayOnTime')
|
const billingType = ref('weekly')
|
||||||
|
const selectedRegion = ref('beijingDC2')
|
||||||
|
const selectedZone = ref('')
|
||||||
|
const selectedGpuModels = ref<string[]>(['RTX 5090'])
|
||||||
const gpuCount = ref(5)
|
const gpuCount = ref(5)
|
||||||
const selectedMachineId = ref('')
|
const selectedMachineId = ref('')
|
||||||
const imageType = ref('SYSTEM')
|
const needExpand = ref(false)
|
||||||
const selectedImage = ref<string>("")
|
const expandSize = ref(100)
|
||||||
|
const imageType = ref('platformImage')
|
||||||
|
const selectedImage = ref<string[]>([])
|
||||||
|
const selectedCoupon = ref('')
|
||||||
|
const tableLoading = ref(false)
|
||||||
const creating = ref(false)
|
const creating = ref(false)
|
||||||
const serviceInfo = ref<any>({})
|
|
||||||
const userInfo = ref<any>({})
|
// 所有机器数据
|
||||||
const HostId=ref(0)
|
const allMachineList = ref<Machine[]>([
|
||||||
const computingPowerPoint=ref(0)
|
{
|
||||||
const couponTotal=ref(0)
|
id: '1b0d49b68a',
|
||||||
onMounted(() => {
|
machineName: '494机',
|
||||||
const userInfoStr = localStorage.getItem("userInfo")
|
gpuModel: 'RTX 5090',
|
||||||
if (userInfoStr) {
|
gpuMemory: '32GB',
|
||||||
userInfo.value = JSON.parse(userInfoStr)
|
freeGpu: 6,
|
||||||
|
totalGpu: 8,
|
||||||
|
cpuCores: 25,
|
||||||
|
memory: 90,
|
||||||
|
cpuModel: 'Xeon(R) Platinum 8470Q',
|
||||||
|
dataDisk: 50,
|
||||||
|
expandableDisk: 7500,
|
||||||
|
driver: '580.76.05',
|
||||||
|
cuda: '13.0',
|
||||||
|
basePrice: 437.00,
|
||||||
|
originalPrice: 460.00,
|
||||||
|
region: 'beijingDC2',
|
||||||
|
zone: 'beijingDC3',
|
||||||
|
availableGpuCount: 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '63af4ab7db',
|
||||||
|
machineName: '518机',
|
||||||
|
gpuModel: 'RTX 5090',
|
||||||
|
gpuMemory: '32GB',
|
||||||
|
freeGpu: 6,
|
||||||
|
totalGpu: 8,
|
||||||
|
cpuCores: 25,
|
||||||
|
memory: 90,
|
||||||
|
cpuModel: 'Xeon(R) Platinum 8470Q',
|
||||||
|
dataDisk: 50,
|
||||||
|
expandableDisk: 7105,
|
||||||
|
driver: '580.76.05',
|
||||||
|
cuda: '13.0',
|
||||||
|
basePrice: 437.00,
|
||||||
|
originalPrice: 460.00,
|
||||||
|
region: 'beijingDC2',
|
||||||
|
zone: 'beijingDC4',
|
||||||
|
availableGpuCount: 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ff5d489b4f',
|
||||||
|
machineName: '495机',
|
||||||
|
gpuModel: 'RTX 5090',
|
||||||
|
gpuMemory: '32GB',
|
||||||
|
freeGpu: 6,
|
||||||
|
totalGpu: 8,
|
||||||
|
cpuCores: 25,
|
||||||
|
memory: 90,
|
||||||
|
cpuModel: 'Xeon(R) Platinum 8470Q',
|
||||||
|
dataDisk: 50,
|
||||||
|
expandableDisk: 6700,
|
||||||
|
driver: '580.76.05',
|
||||||
|
cuda: '13.0',
|
||||||
|
basePrice: 437.00,
|
||||||
|
originalPrice: 460.00,
|
||||||
|
region: 'beijingDC2',
|
||||||
|
zone: 'beijingDC3',
|
||||||
|
availableGpuCount: 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'a1b2c3d4e5',
|
||||||
|
machineName: '600机',
|
||||||
|
gpuModel: 'RTX PRO 6000',
|
||||||
|
gpuMemory: '48GB',
|
||||||
|
freeGpu: 4,
|
||||||
|
totalGpu: 8,
|
||||||
|
cpuCores: 32,
|
||||||
|
memory: 128,
|
||||||
|
cpuModel: 'Xeon(R) Platinum 8470Q',
|
||||||
|
dataDisk: 100,
|
||||||
|
expandableDisk: 8000,
|
||||||
|
driver: '580.76.05',
|
||||||
|
cuda: '13.0',
|
||||||
|
basePrice: 589.00,
|
||||||
|
region: 'westDC3',
|
||||||
|
zone: 'beijingDC4',
|
||||||
|
availableGpuCount: 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'f6g7h8i9j0',
|
||||||
|
machineName: '601机',
|
||||||
|
gpuModel: 'vGPU-48GB',
|
||||||
|
gpuMemory: '48GB',
|
||||||
|
freeGpu: 8,
|
||||||
|
totalGpu: 12,
|
||||||
|
cpuCores: 48,
|
||||||
|
memory: 192,
|
||||||
|
cpuModel: 'Xeon(R) Platinum 8470Q',
|
||||||
|
dataDisk: 150,
|
||||||
|
expandableDisk: 10000,
|
||||||
|
driver: '580.76.05',
|
||||||
|
cuda: '13.0',
|
||||||
|
basePrice: 689.00,
|
||||||
|
region: 'chongqingDC1',
|
||||||
|
zone: 'neimengDC2',
|
||||||
|
availableGpuCount: 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'k1l2m3n4o5',
|
||||||
|
machineName: '602机',
|
||||||
|
gpuModel: 'RTX 4090',
|
||||||
|
gpuMemory: '24GB',
|
||||||
|
freeGpu: 3,
|
||||||
|
totalGpu: 8,
|
||||||
|
cpuCores: 20,
|
||||||
|
memory: 80,
|
||||||
|
cpuModel: 'Xeon(R) Platinum 8470Q',
|
||||||
|
dataDisk: 50,
|
||||||
|
expandableDisk: 5000,
|
||||||
|
driver: '580.76.05',
|
||||||
|
cuda: '13.0',
|
||||||
|
basePrice: 389.00,
|
||||||
|
originalPrice: 410.00,
|
||||||
|
region: 'beijingDC1',
|
||||||
|
zone: 'shanghaiDC1',
|
||||||
|
availableGpuCount: 8
|
||||||
}
|
}
|
||||||
serviceInfo.value = useRoute().query
|
])
|
||||||
HostId.value=serviceInfo.value.id
|
|
||||||
console.log('1111',serviceInfo.value)
|
|
||||||
gpuCountOptions.value = Array.from({ length: Number(serviceInfo.value.gpuAvailableNum) }, (_, i) => i + 1)
|
|
||||||
gpuCount.value = gpuCountOptions.value[0]
|
|
||||||
getServiceImages()
|
|
||||||
getCoupon()
|
|
||||||
fetchTotalCost()
|
|
||||||
})
|
|
||||||
async function getCoupon() {
|
|
||||||
const res:any=await getMyCouponTotal()
|
|
||||||
couponTotal.value = res;
|
|
||||||
}
|
|
||||||
// 封装请求
|
|
||||||
async function fetchTotalCost() {
|
|
||||||
const res: any = await getTotalCost({
|
|
||||||
billingMethod: billingType.value,
|
|
||||||
gpuNum: gpuCount.value,
|
|
||||||
host_id: HostId.value
|
|
||||||
})
|
|
||||||
totalMoney.value = res.amount
|
|
||||||
computingPowerPoint.value=res.computingPowerPoint
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听依赖项变化
|
// GPU型号配置
|
||||||
watch([billingType, gpuCount], () => {
|
const gpuModelConfig = ref<GpuModelOption[]>([
|
||||||
fetchTotalCost()
|
{ value: 'RTX 5090', label: 'RTX 5090', available: 685, total: 3280, regions: ['beijingDC2', 'beijingDC1', 'chongqingDC1'] },
|
||||||
getSingleHost()
|
{ value: 'RTX PRO 6000', label: 'RTX PRO 6000', available: 0, total: 8, regions: ['westDC3'] },
|
||||||
}) // 组件加载时也执行一次
|
{ value: 'vGPU-48GB', label: 'vGPU-48GB', available: 31, total: 250, regions: ['chongqingDC1', 'neimengDC3'] },
|
||||||
const getSingleHost = async () => {
|
{ value: 'vGPU-48GB-425W', label: 'vGPU-48GB-425W', available: 58, total: 160, regions: ['neimengDC3'] },
|
||||||
const res: any = await getHostInfo({
|
{ value: 'RTX 5090 D', label: 'RTX 5090 D', available: 1, total: 11, regions: ['beijingDC2'] },
|
||||||
billingMethod: billingType.value,
|
{ value: 'RTX 4090', label: 'RTX 4090', available: 3, total: 1000, regions: ['beijingDC1', 'foshanDC1'] },
|
||||||
gpuNum: gpuCount.value,
|
{ value: 'CPU', label: 'CPU', available: 0, total: 44, regions: ['beijingDC2', 'beijingDC1'] }
|
||||||
host_id: HostId.value
|
])
|
||||||
})
|
|
||||||
serviceInfo.value = res
|
|
||||||
}
|
|
||||||
|
|
||||||
const getServiceImages = async () => {
|
// 配置数据
|
||||||
try {
|
const regions = ref<RegionOption[]>([
|
||||||
selectedImage.value = ''
|
{ value: 'beijingDC2', label: '北京B区' },
|
||||||
const res = await getImages({ image_type: imageType.value })
|
{ value: 'westDC3', label: '西北B区', tag: { color: 'red', text: 'PRO6000' } },
|
||||||
console.log(res.data)
|
{ value: 'chongqingDC1', label: '重庆A区' },
|
||||||
imageOptions.value = res.data.map((item: any) => ({ id: item.id, label: item.image_name }))
|
{ value: 'neimengDC3', label: '内蒙B区' },
|
||||||
} catch (error) {
|
{ value: 'beijingDC1', label: '北京A区' },
|
||||||
|
{ value: 'foshanDC1', label: '佛山区' }
|
||||||
|
])
|
||||||
|
|
||||||
}
|
const zones = ref<ZoneOption[]>([
|
||||||
}
|
{ value: 'beijingDC4', label: 'L20专区', regions: ['beijingDC2'] },
|
||||||
|
{ value: 'beijingDC3', label: 'V100专区', regions: ['beijingDC2'] },
|
||||||
|
{ value: 'neimengDC2', label: 'A800专区', regions: ['neimengDC3'] },
|
||||||
|
{ value: 'shanghaiDC1', label: '摩尔线程专区', regions: ['beijingDC1'] },
|
||||||
|
{ value: 'guangdongDC1', label: '华为昇腾专区', regions: ['foshanDC1'] }
|
||||||
|
])
|
||||||
|
|
||||||
const gpuCountOptions = ref<number[]>([])
|
const gpuCountOptions = [1, 2, 3, 4, 5, 6, 7, 8, 10, 12]
|
||||||
|
|
||||||
|
// 优惠券数据
|
||||||
|
const availableCoupons = ref<Coupon[]>([
|
||||||
|
{ id: 'coupon1', name: '新用户优惠券', amount: 50, type: 'discount' },
|
||||||
|
{ id: 'coupon2', name: '周年庆优惠', amount: 100, type: 'discount' }
|
||||||
|
])
|
||||||
|
|
||||||
// 镜像选项
|
// 镜像选项
|
||||||
const imageOptions = ref<any>([])
|
const imageOptions = ref([
|
||||||
|
{
|
||||||
|
value: 'pytorch',
|
||||||
|
label: 'PyTorch',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: '1.12.0',
|
||||||
|
label: '1.12.0',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'python3.8',
|
||||||
|
label: 'Python 3.8',
|
||||||
|
children: [
|
||||||
|
{ value: 'cuda11.6', label: 'CUDA 11.6' },
|
||||||
|
{ value: 'cuda11.7', label: 'CUDA 11.7' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
const handleChange = (e: any) => {
|
// 表格列定义
|
||||||
console.log(e)
|
const columns: TableColumnsType = [
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
key: 'action',
|
||||||
|
width: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '主机ID',
|
||||||
|
dataIndex: 'id',
|
||||||
|
key: 'id',
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '算力型号/显存',
|
||||||
|
key: 'gpuModel',
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '空闲GPU',
|
||||||
|
key: 'gpuInfo',
|
||||||
|
width: 100,
|
||||||
|
sorter: (a: Machine, b: Machine) => a.freeGpu - b.freeGpu
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '每GPU分配',
|
||||||
|
key: 'cpuInfo',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'CPU型号',
|
||||||
|
dataIndex: 'cpuModel',
|
||||||
|
key: 'cpuModel',
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '硬盘',
|
||||||
|
key: 'disk',
|
||||||
|
width: 140,
|
||||||
|
sorter: (a: Machine, b: Machine) => a.dataDisk - b.dataDisk
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '驱动/CUDA',
|
||||||
|
key: 'driver',
|
||||||
|
width: 140
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '价格(单卡)',
|
||||||
|
key: 'price',
|
||||||
|
width: 140
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const cpuCores = computed(() => 25 * gpuCount.value)
|
||||||
|
const memory = computed(() => 90 * gpuCount.value)
|
||||||
|
|
||||||
|
// 计费单位
|
||||||
|
const billingUnit = computed(() => {
|
||||||
|
switch (billingType.value) {
|
||||||
|
case 'payg': return '小时'
|
||||||
|
case 'daily': return '天'
|
||||||
|
case 'weekly': return '周'
|
||||||
|
case 'monthly': return '月'
|
||||||
|
default: return '周'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 可用的GPU型号(根据地区筛选)
|
||||||
|
const availableGpuModels = computed(() => {
|
||||||
|
return gpuModelConfig.value.filter(model =>
|
||||||
|
model.regions.includes(selectedRegion.value)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 选中的GPU型号标签
|
||||||
|
const selectedGpuModelLabel = computed(() => {
|
||||||
|
if (selectedGpuModels.value.length === 0) return '未选择'
|
||||||
|
const model = gpuModelConfig.value.find(m => m.value === selectedGpuModels.value[0])
|
||||||
|
return model ? model.label : selectedGpuModels.value[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 过滤后的机器列表
|
||||||
|
const filteredMachineList = computed(() => {
|
||||||
|
let filtered = allMachineList.value.filter(machine => {
|
||||||
|
// 按地区筛选
|
||||||
|
if (machine.region !== selectedRegion.value) return false
|
||||||
|
|
||||||
|
// 按专区筛选(如果有选择专区)
|
||||||
|
if (selectedZone.value && machine.zone !== selectedZone.value) return false
|
||||||
|
|
||||||
|
// 按GPU型号筛选
|
||||||
|
if (selectedGpuModels.value.length > 0 && !selectedGpuModels.value.includes('all')) {
|
||||||
|
if (!selectedGpuModels.value.includes(machine.gpuModel)) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按GPU数量筛选:机器的可用GPU数量要大于等于选择的GPU数量
|
||||||
|
if (machine.availableGpuCount < gpuCount.value) return false
|
||||||
|
|
||||||
|
// 确保有足够的空闲GPU
|
||||||
|
if (machine.freeGpu < gpuCount.value) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
})
|
||||||
|
|
||||||
|
const showZones = computed(() => {
|
||||||
|
return availableZones.value.length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const availableZones = computed(() => {
|
||||||
|
return zones.value.filter(zone => zone.regions.includes(selectedRegion.value))
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalDiskSize = computed(() => {
|
||||||
|
return needExpand.value ? 50 + expandSize.value : 50
|
||||||
|
})
|
||||||
|
|
||||||
|
const maxExpandSize = computed(() => {
|
||||||
|
const machine = allMachineList.value.find(m => m.id === selectedMachineId.value)
|
||||||
|
return machine ? machine.expandableDisk : 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalPrice = computed(() => {
|
||||||
|
const machine = allMachineList.value.find(m => m.id === selectedMachineId.value)
|
||||||
|
if (!machine) return '0.00'
|
||||||
|
|
||||||
|
let price = machine.basePrice * gpuCount.value
|
||||||
|
if (billingType.value === 'payg') price = price / (7 * 24)
|
||||||
|
else if (billingType.value === 'daily') price = price / 7
|
||||||
|
else if (billingType.value === 'monthly') price = price * 4
|
||||||
|
|
||||||
|
return price.toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
const originalTotalPrice = computed(() => {
|
||||||
|
const machine = allMachineList.value.find(m => m.id === selectedMachineId.value)
|
||||||
|
if (!machine || !machine.originalPrice) return ''
|
||||||
|
|
||||||
|
let price = machine.originalPrice * gpuCount.value
|
||||||
|
if (billingType.value === 'payg') price = price / (7 * 24)
|
||||||
|
else if (billingType.value === 'daily') price = price / 7
|
||||||
|
else if (billingType.value === 'monthly') price = price * 4
|
||||||
|
|
||||||
|
return price.toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
const canCreate = computed(() => {
|
||||||
|
return selectedMachineId.value && selectedImage.value.length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 价格计算方法
|
||||||
|
const getPrice = (record: Machine) => {
|
||||||
|
const basePrice = record.basePrice * gpuCount.value
|
||||||
|
switch (billingType.value) {
|
||||||
|
case 'payg': return (basePrice / (7 * 24)).toFixed(2)
|
||||||
|
case 'daily': return (basePrice / 7).toFixed(2)
|
||||||
|
case 'weekly': return basePrice.toFixed(2)
|
||||||
|
case 'monthly': return (basePrice * 4).toFixed(2)
|
||||||
|
default: return basePrice.toFixed(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOriginalPrice = (record: Machine) => {
|
||||||
|
if (!record.originalPrice) return ''
|
||||||
|
const originalPrice = record.originalPrice * gpuCount.value
|
||||||
|
switch (billingType.value) {
|
||||||
|
case 'payg': return (originalPrice / (7 * 24)).toFixed(2)
|
||||||
|
case 'daily': return (originalPrice / 7).toFixed(2)
|
||||||
|
case 'weekly': return originalPrice.toFixed(2)
|
||||||
|
case 'monthly': return (originalPrice * 4).toFixed(2)
|
||||||
|
default: return originalPrice.toFixed(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
const handleRowClick = (record: Machine) => {
|
||||||
|
selectedMachineId.value = record.id
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSelectMachine = (id: string) => {
|
||||||
|
selectedMachineId.value = id
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRowClassName = (record: Machine) => {
|
||||||
|
return selectedMachineId.value === record.id ? 'selected-row' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBillingTypeChange = () => {
|
||||||
|
console.log('计费方式改变:', billingType.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRegionChange = () => {
|
||||||
|
console.log('地区改变:', selectedRegion.value)
|
||||||
|
if (availableGpuModels.value.length > 0) {
|
||||||
|
selectedGpuModels.value = [availableGpuModels.value[0].value]
|
||||||
|
} else {
|
||||||
|
selectedGpuModels.value = []
|
||||||
|
}
|
||||||
|
selectedZone.value = ''
|
||||||
|
selectedMachineId.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleZoneChange = () => {
|
||||||
|
console.log('专区改变:', selectedZone.value)
|
||||||
|
selectedMachineId.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGpuModelChange = () => {
|
||||||
|
console.log('GPU型号改变:', selectedGpuModels.value)
|
||||||
|
selectedMachineId.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGpuCountChange = () => {
|
||||||
|
console.log('GPU数量改变:', gpuCount.value)
|
||||||
|
selectedMachineId.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const isGpuCountDisabled = (count: number) => {
|
||||||
|
const machine = filteredMachineList.value.find(m => m.id === selectedMachineId.value)
|
||||||
|
return machine ? count > machine.availableGpuCount : false
|
||||||
|
}
|
||||||
|
|
||||||
|
const filter = (inputValue: string, path: any[]) => {
|
||||||
|
return path.some(option =>
|
||||||
|
option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isShow=computed(()=>{
|
|
||||||
return userInfo.value.balance<computingPowerPoint.value
|
|
||||||
})
|
|
||||||
const handleCreate = async () => {
|
const handleCreate = async () => {
|
||||||
creating.value = true
|
creating.value = true
|
||||||
try {
|
try {
|
||||||
const params={
|
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||||
host_id:HostId.value,
|
|
||||||
billingMethod:billingType.value,
|
|
||||||
gpuNum:gpuCount.value,
|
|
||||||
case_name:instanceName.value,
|
|
||||||
image_id:selectedImage.value,
|
|
||||||
is_use_voucher:checked.value
|
|
||||||
}
|
|
||||||
const res=await createHost(params)
|
|
||||||
message.success('实例创建成功!')
|
message.success('实例创建成功!')
|
||||||
creating.value=false
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('创建失败,请重试')
|
message.error('创建失败,请重试')
|
||||||
} finally {
|
} finally {
|
||||||
@ -250,7 +702,77 @@ const handleCancel = () => {
|
|||||||
window.history.back()
|
window.history.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听选中的机器列表,自动选择第一个可用的机器
|
||||||
|
watch(filteredMachineList, (newList) => {
|
||||||
|
if (newList.length > 0 && !selectedMachineId.value) {
|
||||||
|
selectedMachineId.value = newList[0].id
|
||||||
|
} else if (newList.length === 0) {
|
||||||
|
selectedMachineId.value = ''
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
// 监听器
|
||||||
|
watch(needExpand, (newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
expandSize.value = 100
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(selectedMachineId, (newId) => {
|
||||||
|
if (newId && needExpand.value) {
|
||||||
|
const machine = allMachineList.value.find(m => m.id === newId)
|
||||||
|
if (machine && expandSize.value > machine.expandableDisk) {
|
||||||
|
expandSize.value = machine.expandableDisk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化时设置默认的GPU型号
|
||||||
|
if (availableGpuModels.value.length > 0) {
|
||||||
|
selectedGpuModels.value = [availableGpuModels.value[0].value]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 价格明细计算
|
||||||
|
const gpuPrice = computed(() => {
|
||||||
|
const machine = allMachineList.value.find(m => m.id === selectedMachineId.value)
|
||||||
|
if (!machine) return '0.00'
|
||||||
|
|
||||||
|
let price = machine.basePrice * gpuCount.value
|
||||||
|
if (billingType.value === 'payg') price = price / (7 * 24)
|
||||||
|
else if (billingType.value === 'daily') price = price / 7
|
||||||
|
else if (billingType.value === 'monthly') price = price * 4
|
||||||
|
|
||||||
|
return price.toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
const diskPrice = computed(() => {
|
||||||
|
if (!needExpand.value || expandSize.value <= 0) return '0.00'
|
||||||
|
// 假设每GB硬盘费用为0.1元/周
|
||||||
|
const pricePerGB = 0.1
|
||||||
|
let price = pricePerGB * expandSize.value
|
||||||
|
if (billingType.value === 'payg') price = price / (7 * 24)
|
||||||
|
else if (billingType.value === 'daily') price = price / 7
|
||||||
|
else if (billingType.value === 'monthly') price = price * 4
|
||||||
|
|
||||||
|
return price.toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
const couponDiscount = computed(() => {
|
||||||
|
if (!selectedCoupon.value) return '0.00'
|
||||||
|
const coupon = availableCoupons.value.find(c => c.id === selectedCoupon.value)
|
||||||
|
return coupon ? coupon.amount.toFixed(2) : '0.00'
|
||||||
|
})
|
||||||
|
|
||||||
|
const showPriceBreakdown = computed(() => {
|
||||||
|
return diskPrice !== '0.00' || couponDiscount !== '0.00'
|
||||||
|
})
|
||||||
|
|
||||||
|
const calculateDiscount = () => {
|
||||||
|
if (!originalTotalPrice.value || totalPrice.value === '0.00') return 0
|
||||||
|
const original = parseFloat(originalTotalPrice.value)
|
||||||
|
const current = parseFloat(totalPrice.value)
|
||||||
|
return Math.round((original - current) / original * 100)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -259,6 +781,7 @@ const handleCancel = () => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
min-height: 100vh;
|
||||||
background: url('@/assets/bgImg.png') no-repeat center / 100% 100%;
|
background: url('@/assets/bgImg.png') no-repeat center / 100% 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,7 +1007,7 @@ const handleCancel = () => {
|
|||||||
border-top: 1px solid #e8e8e8;
|
border-top: 1px solid #e8e8e8;
|
||||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.08);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
@ -503,8 +1026,7 @@ const handleCancel = () => {
|
|||||||
/* 左侧:费用汇总 */
|
/* 左侧:费用汇总 */
|
||||||
.price-summary {
|
.price-summary {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
gap: 40px;
|
||||||
gap: 20px;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,7 +1038,7 @@ const handleCancel = () => {
|
|||||||
.balance-label {
|
.balance-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666;
|
color: #666;
|
||||||
/* margin-bottom: 8px; */
|
margin-bottom: 8px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -84,22 +84,22 @@
|
|||||||
<span class="price-unit">元/卡/小时</span>
|
<span class="price-unit">元/卡/小时</span>
|
||||||
</div>
|
</div>
|
||||||
<a-button type="primary" class="buy-btn" :disabled="record.freeGpu === 0"
|
<a-button type="primary" class="buy-btn" :disabled="record.freeGpu === 0"
|
||||||
@click.stop="handleRentMachine(record)">
|
@click.stop="handleRentMachine(record.id)">
|
||||||
立即购买
|
立即购买
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pagination-container">
|
||||||
|
<a-pagination v-model:current="currentPage" v-model:pageSize="pageSize" :total="hostTotal"
|
||||||
|
show-size-changer show-quick-jumper />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-empty />
|
<a-empty />
|
||||||
</template>
|
</template>
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<div class="pagination-container">
|
|
||||||
<a-pagination v-model:current="currentPage" v-model:pageSize="pageSize" :total="hostTotal"
|
|
||||||
show-size-changer show-quick-jumper />
|
|
||||||
</div>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -186,15 +186,13 @@ const handleSelectMachine = (id: string) => {
|
|||||||
selectedMachineId.value = id
|
selectedMachineId.value = id
|
||||||
}
|
}
|
||||||
// 一卡可租
|
// 一卡可租
|
||||||
const handleRentMachine = (record: any) => {
|
const handleRentMachine = (id: string) => {
|
||||||
router.push({path:"/layout/create",query:record})
|
router.push("/layout/create")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleGpuCountChange = (e:any) => {
|
const handleGpuCountChange = () => {
|
||||||
// console.log(e)
|
console.log('GPU数量改变:', gpuCount.value)
|
||||||
gpuCount.value=e
|
|
||||||
getHostList()
|
|
||||||
selectedMachineId.value = ''
|
selectedMachineId.value = ''
|
||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user