391 lines
8.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="instance-list">
<!-- 顶部标题和操作区域 -->
<div class="header-section">
<div class="header-top">
<div class="header-left">
<span class="page-title">容器实例</span>
<span class="warning-tip">
<exclamation-circle-outlined class="warning-icon" />
实例连续关机15天会释放实例实例释放会导致数据清空且不可恢复释放前实例在数据在
</span>
</div>
<!-- <div class="header-quick-actions">
<span class="quick-action-item">
<bell-outlined class="quick-action-icon" />
订阅GPU通知
</span>
<span class="quick-action-item">
<key-outlined class="quick-action-icon" />
设置密钥登录
</span>
<span class="quick-action-item">
<appstore-outlined class="quick-action-icon" />
小程序管理实例
</span>
</div> -->
</div>
<div class="header-bottom">
<div class="header-actions">
<a-button type="primary" @click="handleRent" class="action-btn">租用新实例</a-button>
<!-- <a-button @click="handleRenew" class="action-btn">批量续费</a-button> -->
<a-button class="refresh-btn">
<reload-outlined />
</a-button>
</div>
<div class="header-filter">
<a-select placeholder="筛选标签" style="width: 160px;" size="large">
<a-select-option value="all">全部标签</a-select-option>
<a-select-option value="running">运行中</a-select-option>
<a-select-option value="stopped">已停止</a-select-option>
</a-select>
<a-input-search placeholder="搜索实例名称/ID" style="width: 240px; margin-left: 12px;" size="large"
@search="onSearch" />
</div>
</div>
</div>
<!-- 表格 -->
<div class="table-container">
<a-table :dataSource="listData" :columns="columns" bordered :pagination="paginationState" @change="onTableChange">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'gpu_model'">
<div>
{{ 'GPU型号:' + record.gpu_model,'GPU数量:' + record.gpu_count ,'单卡显存GB:' + record.gpu_memory_gb , 'CPU核数:' + record.cpu_cores , '内存MB:' + record.memory_mb}}
</div>
</template>
</template>
</a-table>
</div>
<!-- 分页 -->
</div>
</template>
<script lang="ts" setup>
import { ref, onBeforeMount } from 'vue'
import { usePagination } from '@/hooks'
import { hostCaseList } from '@/apis/admin'
import { useRouter } from 'vue-router'
const router = useRouter()
import dayjs from 'dayjs'
const { listData, loading, showLoading, hideLoading, paginationState, resetPagination, searchFormData } = usePagination()
const columns = ref([
{ title: '实例ID', dataIndex: 'id', key: 'id' },
{ title: '名称', dataIndex: 'name', key: 'name' },
{ title: '状态', dataIndex: 'status', key: 'status' },
{ title: '规格详情', dataIndex: 'gpu_model', key: 'gpu_model' },
{ title: '本地磁盘', dataIndex: 'system_disk', key: 'system_disk' },
{ title: '健康状态', dataIndex: 'health_status', key: 'health_status' },
{ title: '付费方式', dataIndex: 'price_type', key: 'price_type' },
{ title: '释放时间', dataIndex: 'release_at', key: 'release_at' },
{ title: '停机时间', dataIndex: 'down_at', key: 'down_at' },
{ title: 'SSH登陆', dataIndex: 'ssh_link', key: 'ssh_link' },
{ title: '操作', dataIndex: 'aciton', key: 'aciton' },
])
onBeforeMount(() => {
getPageList()
})
const getPageList = async () => {
try {
const { pageSize, current } = paginationState
const res: any = await hostCaseList({ pageSize: pageSize, pageNum: current });
listData.value = res.list;
paginationState.total = res?.Total;
} catch (error: any) {
console.error('产品优势请求失败:', error);
}
}
/**
* 分页
*/
function onTableChange({ current, pageSize }) {
paginationState.current = current
paginationState.pageSize = pageSize
getPageList()
}
/**
* 搜索
*/
function handleSearch() {
resetPagination()
getPageList()
}
/**
* 重置
*/
function handleResetSearch() {
searchFormData.value = {}
resetPagination()
getPageList()
}
// 租用实例
const handleRent = () => {
router.push('/layout/admin/instanceCreate');
}
</script>
<style scoped lang="scss">
.instance-list {
padding: 24px;
background: #f5f7fa;
// min-height: 100vh;
}
.header-section {
background: #fff;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #e8e8e8;
}
.header-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 16px;
.header-left {
display: flex;
align-items: flex-start;
gap: 16px;
flex: 1;
min-width: 300px;
.page-title {
font-size: 20px;
font-weight: 600;
color: #1f2d3d;
line-height: 1.4;
white-space: nowrap;
}
.warning-tip {
display: flex;
align-items: flex-start;
color: #ff7f00;
font-size: 14px;
line-height: 1.4;
flex: 1;
margin-top: 4px;
.warning-icon {
margin-right: 8px;
font-size: 16px;
margin-top: 2px;
flex-shrink: 0;
}
}
}
.header-quick-actions {
display: flex;
gap: 24px;
align-items: center;
flex-wrap: wrap;
.quick-action-item {
display: flex;
align-items: center;
color: #1890ff;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: color 0.3s;
white-space: nowrap;
&:hover {
color: #40a9ff;
}
.quick-action-icon {
margin-right: 6px;
font-size: 14px;
}
}
}
}
.header-bottom {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 16px;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
.header-actions {
display: flex;
gap: 12px;
align-items: center;
.action-btn {
height: 36px;
padding: 0 16px;
font-weight: 500;
}
.refresh-btn {
height: 36px;
width: 36px;
display: flex;
align-items: center;
justify-content: center;
}
}
.header-filter {
display: flex;
gap: 12px;
align-items: center;
}
}
.table-container {
background: #fff;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e8e8e8;
}
.instance-table {
.empty-state {
padding: 60px 20px;
text-align: center;
.empty-icon {
font-size: 64px;
color: #d9d9d9;
margin-bottom: 16px;
}
.empty-text {
font-size: 16px;
color: #8c8c8c;
margin-bottom: 8px;
}
.empty-desc {
font-size: 14px;
color: #bfbfbf;
}
}
// 状态样式
:deep(.status-running) {
color: #52c41a;
background: #f6ffed;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
:deep(.status-stopped) {
color: #ff4d4f;
background: #fff2f0;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
}
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 24px;
padding: 16px 20px;
background: #fff;
border-radius: 8px;
border: 1px solid #e8e8e8;
.pagination-left {
.total-text {
color: #8c8c8c;
font-size: 14px;
}
}
.pagination-center {
flex: 1;
display: flex;
justify-content: center;
}
.pagination-right {
display: flex;
align-items: center;
.goto-text {
color: #8c8c8c;
font-size: 14px;
}
}
}
// 响应式设计
@media (max-width: 1200px) {
.header-top {
.header-left {
flex-direction: column;
gap: 12px;
}
.header-quick-actions {
gap: 16px;
}
}
}
@media (max-width: 768px) {
.instance-list {
padding: 16px;
}
.header-top {
flex-direction: column;
align-items: stretch;
.header-left {
min-width: auto;
}
.header-quick-actions {
justify-content: flex-start;
}
}
.header-bottom {
flex-direction: column;
align-items: stretch;
.header-actions {
justify-content: flex-start;
}
.header-filter {
justify-content: flex-start;
}
}
.pagination-container {
flex-direction: column;
gap: 16px;
.pagination-left,
.pagination-right {
width: 100%;
justify-content: center;
}
}
}
</style>