feat(web): add API client, router, and app layout

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
dailz
2026-04-16 22:45:23 +08:00
parent 01a1119d13
commit 1356f91b9d
5 changed files with 132 additions and 0 deletions

39
web/src/App.vue Normal file
View File

@@ -0,0 +1,39 @@
<template>
<el-config-provider :locale="zhCn">
<el-container style="height: 100vh">
<el-aside width="200px" style="border-right: 1px solid #e6e6e6">
<div style="padding: 16px; font-size: 16px; font-weight: 600; text-align: center">
HPC 集群管理
</div>
<el-menu :router="true" :default-active="route.path">
<el-menu-item index="/jobs">
<el-icon><List /></el-icon>
<span>任务列表</span>
</el-menu-item>
<el-menu-item index="/jobs/history">
<el-icon><Timer /></el-icon>
<span>任务历史</span>
</el-menu-item>
<el-menu-item index="/jobs/submit">
<el-icon><CirclePlus /></el-icon>
<span>提交任务</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-main>
<router-view />
</el-main>
</el-container>
</el-config-provider>
</template>
<script setup lang="ts">
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { List, Timer, CirclePlus } from '@element-plus/icons-vue'
const route = useRoute()
onMounted(() => {
document.title = 'HPC 集群管理'
})
</script>

24
web/src/api/client.ts Normal file
View File

@@ -0,0 +1,24 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
import type { ApiResponse } from '@/types/jobs'
const apiClient = axios.create({
baseURL: '/api/v1',
})
apiClient.interceptors.response.use(
(response) => {
return response.data
},
(error) => {
if (error.response?.data) {
const apiError = error.response.data as ApiResponse<unknown>
ElMessage.error(apiError.error || '请求失败')
} else {
ElMessage.error('无法连接到后端服务')
}
return Promise.reject(error)
}
)
export default apiClient

22
web/src/api/jobs.ts Normal file
View File

@@ -0,0 +1,22 @@
import apiClient from './client'
import type { ApiResponse, SubmitJobRequest, JobResponse, JobListResponse, JobHistoryParams } from '@/types/jobs'
export function submitJob(data: SubmitJobRequest): Promise<ApiResponse<JobResponse>> {
return apiClient.post('/jobs/submit', data)
}
export function getJobs(params?: { page?: number; page_size?: number }): Promise<ApiResponse<JobListResponse>> {
return apiClient.get('/jobs', { params })
}
export function getJobHistory(params?: JobHistoryParams): Promise<ApiResponse<JobListResponse>> {
return apiClient.get('/jobs/history', { params })
}
export function getJob(id: string): Promise<ApiResponse<JobResponse>> {
return apiClient.get(`/jobs/${id}`)
}
export function cancelJob(id: string): Promise<ApiResponse<{ message: string }>> {
return apiClient.delete(`/jobs/${id}`)
}

6
web/src/main.ts Normal file
View File

@@ -0,0 +1,6 @@
import { createApp } from 'vue'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')

41
web/src/router/index.ts Normal file
View File

@@ -0,0 +1,41 @@
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
redirect: '/jobs',
},
{
path: '/jobs',
name: 'JobsList',
component: () => import('@/views/Jobs/List.vue'),
meta: { title: '任务列表' },
},
{
path: '/jobs/history',
name: 'JobsHistory',
component: () => import('@/views/Jobs/History.vue'),
meta: { title: '任务历史' },
},
{
path: '/jobs/submit',
name: 'JobsSubmit',
component: () => import('@/views/Jobs/Submit.vue'),
meta: { title: '提交任务' },
},
{
path: '/jobs/:id',
name: 'JobsDetail',
component: () => import('@/views/Jobs/Detail.vue'),
meta: { title: '任务详情' },
},
{
path: '/:pathMatch(.*)*',
redirect: '/jobs',
},
],
})
export default router