Merge branch 'main' of https://gitlab.guxuan.icu/Leo_Ding/GPU_Web
This commit is contained in:
commit
e8b17d95d7
@ -3,14 +3,7 @@ import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
|
|||||||
import Layout from "@/components/Layout.vue";
|
import Layout from "@/components/Layout.vue";
|
||||||
import { Components } from "ant-design-vue/es/date-picker/generatePicker";
|
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[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
@ -57,6 +50,16 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: "AdminHome",
|
name: "AdminHome",
|
||||||
component: () => import("@/views/admin/home/index.vue"),
|
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",
|
path: "fileStore",
|
||||||
name: "FileStore",
|
name: "FileStore",
|
||||||
|
|||||||
@ -43,7 +43,7 @@ interface MenuItem {
|
|||||||
|
|
||||||
const menuItems: MenuItem[] = [
|
const menuItems: MenuItem[] = [
|
||||||
{ path: '/layout/overview', name: '总览', icon: HomeOutlined },
|
{ 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/fileStore', name: '文件存储', icon: FolderOpenOutlined },
|
||||||
{ path: '/layout/admin/image', name: '镜像', icon: GlobalOutlined },
|
{ path: '/layout/admin/image', name: '镜像', icon: GlobalOutlined },
|
||||||
{ path: '/layout/publicData', name: '公开数据', icon: LaptopOutlined },
|
{ path: '/layout/publicData', name: '公开数据', icon: LaptopOutlined },
|
||||||
|
|||||||
518
src/views/admin/instance/index.vue
Normal file
518
src/views/admin/instance/index.vue
Normal file
@ -0,0 +1,518 @@
|
|||||||
|
<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';
|
||||||
|
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
|
||||||
|
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 router = useRouter()
|
||||||
|
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 = () => {
|
||||||
|
router.push('/layout/admin/instanceCreate');
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
1248
src/views/admin/instanceCreate/index.vue
Normal file
1248
src/views/admin/instanceCreate/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user