Compare commits

...

2 Commits

Author SHA1 Message Date
qiuyuan
8f9a63eac3 Merge branch 'main' of https://gitlab.guxuan.icu/Leo_Ding/GPU_Web 2025-11-25 14:49:20 +08:00
qiuyuan
cae4b2b914 容器实例 2025-11-25 14:49:18 +08:00
4 changed files with 525 additions and 9 deletions

View File

@ -3,14 +3,7 @@ import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import Layout from "@/components/Layout.vue";
import { Components } from "ant-design-vue/es/date-picker/generatePicker";
const HomeView = () => import("@/views/home/index.vue");
const FileStore = () => import("@/views/controlPanel/fileStore/index.vue");
// 账号安全
const AccountSecurity = () =>
import("@/views/controlPanel/account/security/index.vue");
// 访问记录
const AccountHistory = () =>
import("@/views/controlPanel/account/history/index.vue");
const routes: RouteRecordRaw[] = [
{
@ -57,6 +50,16 @@ const routes: RouteRecordRaw[] = [
name: "AdminHome",
component: () => import("@/views/admin/home/index.vue"),
},
{
path: "instance",
name: "Instance",
component: () => import("@/views/admin/instance/index.vue"),
},
{
path: "instanceCreate",
name: "InstanceCreate",
component: () => import("@/views/admin/instanceCreate/index.vue"),
},
{
path: "fileStore",
name: "FileStore",

View File

@ -43,7 +43,7 @@ interface MenuItem {
const menuItems: MenuItem[] = [
{ path: '/layout/overview', name: '总览', icon: HomeOutlined },
{ path: '/layout/container', name: '容器实例', icon: ConsoleSqlOutlined },
{ path: '/layout/admin/instance', name: '容器实例', icon: ConsoleSqlOutlined },
{ path: '/layout/admin/fileStore', name: '文件存储', icon: FolderOpenOutlined },
{ path: '/layout/admin/image', name: '镜像', icon: GlobalOutlined },
{ path: '/layout/publicData', name: '公开数据', icon: LaptopOutlined },

View File

@ -0,0 +1,513 @@
<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
:columns="columns"
:data-source="dataSource"
:pagination="false"
:loading="loading"
row-key="id"
class="instance-table"
>
<template #empty>
<div class="empty-state">
<div class="empty-icon">
<inbox-outlined />
</div>
<div class="empty-text">暂无数据</div>
<div class="empty-desc">当前没有实例数据您可以租用新实例开始使用</div>
</div>
</template>
</a-table>
</div>
<!-- 分页 -->
<div class="pagination-container">
<div class="pagination-left">
<span class="total-text"> {{ total }} </span>
</div>
<div class="pagination-center">
<a-pagination
v-model:current="currentPage"
v-model:page-size="pageSize"
:total="total"
:page-size-options="['10', '20', '50', '100']"
show-size-changer
show-quick-jumper
size="small"
/>
</div>
<div class="pagination-right">
<span class="goto-text">前往</span>
<a-input-number
v-model:value="currentPage"
:min="1"
:max="Math.ceil(total / pageSize)"
size="small"
style="width: 60px; margin: 0 8px;"
/>
<span class="goto-text"></span>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, h } from 'vue';
import { Button, Table, Input, Select, Pagination, InputNumber } from 'ant-design-vue';
import {
ReloadOutlined,
ExclamationCircleOutlined,
BellOutlined,
KeyOutlined,
AppstoreOutlined,
InboxOutlined
} from '@ant-design/icons-vue';
export default defineComponent({
name: 'InstanceList',
components: {
AButton: Button,
ATable: Table,
AInput: Input,
AInputSearch: Input.Search,
ASelect: Select,
ASelectOption: Select.Option,
APagination: Pagination,
AInputNumber: InputNumber,
ReloadOutlined,
ExclamationCircleOutlined,
BellOutlined,
KeyOutlined,
AppstoreOutlined,
InboxOutlined,
},
setup() {
const loading = ref(false);
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
//
const columns = [
{
title: '实例ID / 名称',
dataIndex: 'id',
key: 'id',
width: 200,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 120,
customRender: ({ text }: { text: string }) => {
return h('span', { class: `status-${text.toLowerCase()}` }, text);
},
},
{
title: '规格详情',
dataIndex: 'spec',
key: 'spec',
width: 150,
},
{
title: '本地磁盘',
dataIndex: 'disk',
key: 'disk',
width: 120,
},
{
title: '健康状态',
dataIndex: 'health',
key: 'health',
width: 120,
},
{
title: '付费方式',
dataIndex: 'payment',
key: 'payment',
width: 120,
},
{
title: '释放时间/停机时间',
dataIndex: 'releaseTime',
key: 'releaseTime',
width: 180,
},
{
title: 'SSH登录',
dataIndex: 'ssh',
key: 'ssh',
width: 120,
},
{
title: '快捷工具',
dataIndex: 'tools',
key: 'tools',
width: 120,
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
width: 150,
},
];
//
const dataSource = ref([]);
//
const fetchInstances = () => {
loading.value = true;
setTimeout(() => {
dataSource.value = [];
total.value = 0;
loading.value = false;
}, 500);
};
fetchInstances();
const handleRent = () => {
console.log('租用新实例');
};
const handleRenew = () => {
console.log('批量续费');
};
const onSearch = (value: string) => {
console.log('搜索:', value);
};
return {
columns,
dataSource,
loading,
currentPage,
pageSize,
total,
handleRent,
handleRenew,
onSearch,
};
},
});
</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>

View File