This commit is contained in:
qiuyuan 2025-12-04 16:44:48 +08:00
commit d663140570
13 changed files with 244 additions and 203 deletions

View File

@ -2,5 +2,5 @@
NODE_ENV=development
# api
VITE_API_BASIC="http://10.10.1.27:8888"
VITE_API_BASIC="http://10.10.1.29:8888"

10
src/apis/index.ts Normal file
View File

@ -0,0 +1,10 @@
const modules:any = import.meta.glob('./modules/*.ts', { eager: true })
const api:any = {}
Object.keys(modules).forEach((key) => {
const name = key.slice(key.lastIndexOf('/') + 1, key.lastIndexOf('.'))
api[name] = { ...modules[key] }
})
export default api

4
src/apis/modules/imgs.ts Normal file
View File

@ -0,0 +1,4 @@
import request from '@/utils/index'
// 获取镜像列表
export const fetchImageList = (params:any) => request.get('/v1/image/list', { params })

View File

@ -113,7 +113,7 @@ import {
import { Modal, message } from 'ant-design-vue';
// <script setup> router
import { useRouter } from 'vue-router';
import { updatePassword, updatePhoneNumber } from '@/apis/login';
import { updatePassword, updatePhoneNumber } from '@/apis/modules/login';
const router = useRouter();
const userPhone = ref('')

View File

@ -13,7 +13,7 @@
<div class="stats-row">
<div class="stat-item">
<div class="stat-label">容器实例</div>
<div class="stat-value">0</div>
<div class="stat-value">{{ userInfo.caseNum }}</div>
</div>
<div class="stat-item">
<div class="stat-label">运行中</div>
@ -151,8 +151,8 @@
<!-- 用户信息卡片 -->
<a-card :bordered="false" class="card">
<div class="user-info">
<div class="user-name">炼丹师5075</div>
<a-tag color="orange">未实名</a-tag>
<div class="user-name">{{ userInfo.userName }}</div>
<a-tag color="orange">{{ userInfo.accountType === 'USER' ? '个人认证' : '企业认证' }}</a-tag>
</div>
<div class="user-member">
<UserOutlined class="member-icon" />
@ -179,10 +179,10 @@
<div class="fee-info">
<div class="fee-item">
<span>可用</span>
<span class="fee-value">¥0.00</span>
<span class="fee-value">¥{{ userInfo.noFreezeBalace }}</span>
<span class="fee-divider">|</span>
<span>冻结</span>
<span class="fee-value">¥0.00</span>
<span class="fee-value">¥{{ userInfo.freezeBalace }}</span>
</div>
<div class="fee-item">
<GiftOutlined class="fee-icon" />
@ -214,7 +214,7 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref,onBeforeMount } from 'vue'
import {
UserOutlined,
GiftOutlined,
@ -222,11 +222,17 @@ import {
CreditCardOutlined,
QuestionCircleOutlined
} from '@ant-design/icons-vue'
import { on } from 'events'
//
const switch1 = ref(true)
const switch2 = ref(false)
const userInfo=ref<any>({})
onBeforeMount(() => {
userInfo.value = JSON.parse(localStorage.getItem('userInfo') || '{}');
console.log(userInfo.value)
})
//
const faqList = ref([
{ id: 1, question: '如何选择GPU?' },
@ -240,7 +246,7 @@ const faqList = ref([
.home-page {
padding: 15px;
background: #fff;
min-height: 100vh;
// min-height: 100vh;
}
.card {

View File

@ -0,0 +1,79 @@
<template>
<a-modal v-model:open="open" :title="imgInfo.name" @ok="handleOk" style="width: 600px;">
<a-card>
<a-row :gutter="[16, 16]" class="info-list">
<!-- 名称 -->
<a-col :span="12">
<label class="label">名称</label>
<span class="value">{{ imgInfo.name }}</span>
</a-col>
<!-- CPU类型 -->
<a-col :span="12">
<label class="label">CPU类型</label>
<span class="value">{{ imgInfo.cpu_type }}</span>
</a-col>
<!-- CUDA版本 -->
<a-col :span="12">
<label class=" label">CUDA版本</label>
<span class="value">{{ imgInfo.cuda_version }}</span>
</a-col>
<!-- GPU类型 -->
<a-col :span="12">
<label class="label">GPU类型</label>
<span class="value">{{ imgInfo.gpu_type }}</span>
</a-col>
<!-- 镜像 Hash -->
<a-col :span="24">
<label class="label">镜像 Hash</label>
<span class="value">{{ imgInfo.image_hash }}</span>
</a-col>
<!-- 镜像路径 -->
<a-col :span="24">
<label class="label">镜像路径</label>
<span class="value">{{ imgInfo.image_path }}</span>
</a-col>
<!-- 镜像大小 -->
<a-col :span="12">
<label class="label">镜像大小</label>
<span class="value">{{ imgInfo.image_size }} GB</span>
</a-col>
<!-- 镜像类型 -->
<a-col :span="12">
<label class="label">镜像类型</label>
<span class="value">{{ imgInfo.image_type }}</span>
</a-col>
<!-- 状态 -->
<a-col :span="12">
<label class="label">状态</label>
<a-tag >
{{ imgInfo.status=='enabled'?'启用':'禁用' }}
</a-tag>
</a-col>
<!-- 镜像是否公开 -->
<a-col :span="12">
<label class="label">镜像是否公开</label>
<a-tag :color="imgInfo.image_is_public ? 'green' : 'red'">
{{ imgInfo.image_is_public ? '是' : '否' }}
</a-tag>
</a-col>
</a-row>
</a-card>
</a-modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const imgInfo = ref<any>({});
const open = ref(false);
const handleOk = () => {
open.value = false;
};
defineExpose({
openDialog(item: any) {
imgInfo.value = item;
open.value = true;
}
});
</script>

View File

@ -1,163 +1,3 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import {
Table,
Pagination,
Progress,
message,
Button
} from 'ant-design-vue';
//
interface ImageItem {
uid: string;
name: string;
size: string;
status: string;
shareInfo: string;
source: string;
region: string;
baseImage: string;
createTime: string;
}
//
const imageList = ref<ImageItem[]>([
{
uid: 'img-001',
name: 'Ubuntu 20.04',
size: '2.5GB',
status: '可用',
shareInfo: '私有',
source: '系统镜像',
region: '华北1',
baseImage: 'ubuntu:20.04',
createTime: '2023-10-01 10:30:00'
},
{
uid: 'img-002',
name: 'CentOS 7',
size: '1.8GB',
status: '可用',
shareInfo: '共享',
source: '自定义',
region: '华东2',
baseImage: 'centos:7',
createTime: '2023-10-02 14:20:00'
},
{
uid: 'img-003',
name: 'Windows Server 2019',
size: '4.2GB',
status: '构建中',
shareInfo: '私有',
source: '自定义',
region: '华南1',
baseImage: 'windows:2019',
createTime: '2023-10-03 16:45:00'
},
{
uid: 'img-004',
name: 'Debian 11',
size: '1.9GB',
status: '可用',
shareInfo: '私有',
source: '系统镜像',
region: '华北2',
baseImage: 'debian:11',
createTime: '2023-10-04 09:15:00'
},
{
uid: 'img-005',
name: 'AlmaLinux 8',
size: '2.1GB',
status: '可用',
shareInfo: '共享',
source: '自定义',
region: '华东1',
baseImage: 'almalinux:8',
createTime: '2023-10-05 11:30:00'
}
]);
//
const storageUsed = ref('12.5GB');
const storagePeak = ref('13.2GB');
const expectedFee = ref('0元');
const freeStorage = ref('30.00GB');
const paidStorage = ref('0GB');
//
const storagePercent = computed(() => {
const usedGB = parseFloat(storageUsed.value.replace('GB', ''));
const totalGB = 30;
return Math.min(100, Math.round((usedGB / totalGB) * 100));
});
//
const pagination = reactive({
current: 1,
pageSize: 10,
total: imageList.value.length,
});
//
const loading = ref(false);
//
const columns = [
{ title: '镜像UUID', dataIndex: 'uid', key: 'uid', width: 150 },
{ title: '镜像名称', dataIndex: 'name', key: 'name', width: 150 },
{ title: '大小', dataIndex: 'size', key: 'size', width: 100 },
{ title: '状态', dataIndex: 'status', key: 'status', width: 100 },
{ title: '共享信息', dataIndex: 'shareInfo', key: 'shareInfo', width: 100 },
{ title: '来源', dataIndex: 'source', key: 'source', width: 100 },
{ title: '缓存地区', dataIndex: 'region', key: 'region', width: 120 },
{ title: '原基础镜像信息', dataIndex: 'baseImage', key: 'baseImage', width: 180 },
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 160 },
{
title: '操作',
key: 'action',
slots: { customRender: 'action' },
width: 120
},
];
//
const onPageChange = (page: number, pageSize: number) => {
console.log(`切换到第 ${page} 页,每页 ${pageSize}`);
pagination.current = page;
pagination.pageSize = pageSize;
};
const onShowSizeChange = (current: number, pageSize: number) => {
console.log(`每页显示 ${pageSize}`);
pagination.current = current;
pagination.pageSize = pageSize;
};
//
const handleView = (record: ImageItem) => {
message.info(`查看镜像: ${record.name}`);
};
const handleDelete = (record: ImageItem) => {
message.info(`删除镜像: ${record.name}`);
};
//
const getCurrentPageData = computed(() => {
const start = (pagination.current - 1) * pagination.pageSize;
const end = start + pagination.pageSize;
return imageList.value.slice(start, end);
});
onMounted(() => {
console.log('MyImages 组件已挂载');
});
import { computed } from 'vue';
</script>
<template>
<div class="my-images-page">
@ -192,20 +32,19 @@ import { computed } from 'vue';
<!-- 表格 -->
<div class="table-container">
<a-table
:data-source="getCurrentPageData"
:columns="columns"
:pagination="false"
:scroll="{ x: 'max-content', y: 400 }"
:row-key="(record) => record.uid"
:loading="loading"
class="image-table"
>
<a-table :data-source="getCurrentPageData" :columns="columns" :pagination="false"
:scroll="{ x: 'max-content', y: 400 }" :row-key="(record) => record.uid" :loading="loading" bordered
class="image-table">
<template #action="{ record }">
<a-button type="link" size="small" @click="handleView(record)">查看</a-button>
<a-button type="link" size="small" danger @click="handleDelete(record)">删除</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'status'">
<a-tag v-if="record.status == 'enabled'" color="green">可用</a-tag>
<a-tag v-if="record.status == 'disabled'" color="red">不可用</a-tag>
</template>
</template>
<template #empty>
<div class="table-empty">暂无数据</div>
</template>
@ -214,26 +53,128 @@ import { computed } from 'vue';
<!-- 分页固定在底部 -->
<div class="pagination-container">
<a-pagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
show-size-changer
show-jumper
show-less-items
:page-size-options="[5, 10, 20, 50]"
@change="onPageChange"
@showSizeChange="onShowSizeChange"
/>
<a-pagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize"
:total="pagination.total" show-size-changer show-jumper show-less-items :page-size-options="[5, 10, 20, 50]"
@change="onPageChange" @showSizeChange="onShowSizeChange" />
</div>
<editDialog ref="editDialogRef" />
</div>
</template>
<script setup lang="ts">
import editDialog from './editDialog.vue';
import { ref, reactive, onMounted, computed, } from 'vue';
import {
Table,
Pagination,
Progress,
message,
Button
} from 'ant-design-vue';
import apis from '@/apis';
//
interface ImageItem {
uid: string;
name: string;
size: string;
status: string;
shareInfo: string;
source: string;
region: string;
baseImage: string;
createTime: string;
}
//
const imageList = ref<ImageItem[]>([]);
const editDialogRef = ref<any>(null);
//
const storageUsed = ref('12.5GB');
const storagePeak = ref('13.2GB');
const expectedFee = ref('0元');
const freeStorage = ref('30.00GB');
const paidStorage = ref('0GB');
//
const storagePercent = computed(() => {
const usedGB = parseFloat(storageUsed.value.replace('GB', ''));
const totalGB = 30;
return Math.min(100, Math.round((usedGB / totalGB) * 100));
});
//
const pagination = reactive({
current: 1,
pageSize: 10,
total: imageList.value.length,
});
//
const loading = ref(false);
//
const columns = [
{ title: '名称', dataIndex: 'name', key: 'name', width: 150 },
{ title: 'cpu类型', dataIndex: 'cpu_type', key: 'cpu_type', width: 150 },
{ title: 'cuda版本', dataIndex: 'cuda_version', key: 'cuda_version', width: 100 },
{ title: 'gpu类型', dataIndex: 'gpu_type', key: 'gpu_type', width: 100 },
{ title: '镜像hash', dataIndex: 'image_hash', key: 'image_hash', width: 100 },
{ title: '镜像是否公开', dataIndex: 'image_is_public', key: 'image_is_public', width: 130 },
{ title: '镜像路径', dataIndex: 'image_path', key: 'image_path', width: 120 },
{ title: '镜像大小', dataIndex: 'image_size', key: 'image_size', width: 180 },
{ title: '镜像类型', dataIndex: 'image_type', key: 'image_type', width: 180 },
{ title: '状态', dataIndex: 'status', key: 'status', width: 80 },
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 160 },
{
title: '操作',
key: 'action',
slots: { customRender: 'action' },
fixed: 'right',
width: 130
},
];
//
const onPageChange = (page: number, pageSize: number) => {
console.log(`切换到第 ${page} 页,每页 ${pageSize}`);
pagination.current = page;
pagination.pageSize = pageSize;
};
const onShowSizeChange = (current: number, pageSize: number) => {
console.log(`每页显示 ${pageSize}`);
pagination.current = current;
pagination.pageSize = pageSize;
};
//
const handleView = (record: ImageItem) => {
// message.info(`: ${record.name}`);
editDialogRef.value.openDialog(record);
};
const handleDelete = (record: ImageItem) => {
message.info(`删除镜像: ${record.name}`);
};
//
const getCurrentPageData = computed(() => {
const start = (pagination.current - 1) * pagination.pageSize;
const end = start + pagination.pageSize;
return imageList.value.slice(start, end);
});
onMounted(() => {
apis.imgs.fetchImageList({ pageNum: pagination.current, pageSize: pagination.pageSize }).then((response: any) => {
imageList.value = response.list;
});
});
</script>
<style scoped lang="scss">
.my-images-page {
padding: 24px;
background-color: #f5f7fa;
min-height: 100vh;
background-color: #ffffff;
// min-height: 100vh;
display: flex;
flex-direction: column;
@ -274,6 +215,7 @@ import { computed } from 'vue';
.storage-desc {
font-size: 14px;
strong {
font-size: 16px;
color: #1890ff;
@ -340,11 +282,9 @@ import { computed } from 'vue';
.pagination-container {
margin-top: auto;
padding-top: 24px;
text-align: center;
text-align: right;
border-top: 1px solid #e8e8e8;
}
}
</style>

View File

@ -77,7 +77,7 @@ const menuItems: MenuItem[] = [
const state = reactive({
mode: 'inline' as MenuMode,
theme: 'light' as MenuTheme,
selectedKeys: ['/controlPanel/overview'], //
selectedKeys: ['/layout/admin/home'], //
openKeys: ['/controlPanel/fee', '/controlPanel/account'], //
});
@ -129,7 +129,7 @@ const changeTheme = (checked: boolean) => {
<style scoped>
.admin-layout {
display: flex;
height: 100vh; /* 全屏高度 */
height: calc(100vh - 60px); /* 全屏高度 */
width: 100vw;
overflow: hidden;
}

View File

@ -3,6 +3,7 @@
<div :class="isHome ? 'gx_layout_header_home' : 'gx_layout_header_noHome'" class="gx_layout_header">
<div class="logo">GxDL算力云</div>
</div>
<div class="gx_layout_content">
<div>
<a-menu id="dddddd" v-model:selectedKeys="selectedKeys" style="width: 256px;height: 100%;" mode="inline"
@ -107,7 +108,7 @@ const handleMenuClick = (key) => {
}
.gx_layout_content {
margin-top: 60px;
// margin-top: 60px;
height: calc(100% - 60px);
background-color: rgba(240, 240, 240, 1);
display: flex;

View File

@ -44,6 +44,7 @@
</a-dropdown>
</div>
</div>
<div style="height: 60px;width: 100%;"></div>
<div class="gx_layout_content">
<router-view />
</div>
@ -159,7 +160,7 @@ const logout = () => {
}
.gx_layout_content {
margin-top: 60px;
// margin-top: 60px;
min-height: calc(100% - 60px);
background-color: rgba(240, 240, 240, 1);
}

View File

@ -19,7 +19,7 @@
<script lang="ts" setup>
import { reactive, ref, shallowRef } from 'vue';
import { useRouter } from 'vue-router';
import { login, fetchUserInfo } from '@/apis/login';
import { login, fetchUserInfo } from '@/apis/modules/login';
import { message, type FormInstance } from 'ant-design-vue';
const router = useRouter();

View File

@ -19,6 +19,6 @@
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/apis/index.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}