final version

This commit is contained in:
2026-02-18 23:39:39 +08:00
parent 72ab0c0b56
commit 32d0bca5f9
3975 changed files with 781 additions and 1106509 deletions

View File

@@ -102,14 +102,6 @@ const routes: Array<RouteRecordRaw> = [
meta: {
title: '配置管理'
}
},
{
path: 'comparison',
name: 'AdminAlgorithmComparison',
component: () => import('../views/admin/AdminAlgorithmComparisonView.vue'),
meta: {
title: '算法效果比较'
}
}
]
},
@@ -129,7 +121,6 @@ const routes: Array<RouteRecordRaw> = [
title: '注册'
}
},
// 404页面
{
path: '/:pathMatch(.*)*',
name: 'NotFound',

View File

@@ -42,7 +42,7 @@ export interface ComparisonReport {
}
class ConfigService {
private readonly baseUrl = '/api/config'
private readonly baseUrl = '/api/v1/config'
async getConfig(configKey: string): Promise<ConfigItem | null> {
try {
@@ -111,7 +111,7 @@ class ConfigService {
}
class ComparisonService {
private readonly baseUrl = '/api/comparison'
private readonly baseUrl = '/api/v1/comparison'
async compareAlgorithms(inputData: any, algorithmConfigs: AlgorithmConfig[]): Promise<ComparisonResult> {
try {

View File

@@ -219,6 +219,28 @@ export const apiManagementService = {
throw new Error(error.detail || '测试API端点失败')
}
return await response.json()
},
async syncApiEndpoints(): Promise<{ success: boolean; message: string }> {
const token = localStorage.getItem('token')
if (!token) {
throw new Error('未登录')
}
const response = await fetch('http://0.0.0.0:8001/api/v1/services/sync-api-endpoints', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || '同步API端点失败')
}
return await response.json()
}
}

View File

@@ -72,7 +72,7 @@ export const useAlgorithmStore = defineStore('algorithm', {
try {
const params = type ? { type } : {}
const response = await axios.get('/api/algorithms', { params })
const response = await axios.get('/api/v1/algorithms', { params })
this.algorithms = response.data.algorithms
return true
} catch (error: any) {
@@ -89,7 +89,7 @@ export const useAlgorithmStore = defineStore('algorithm', {
this.error = null
try {
const response = await axios.get(`/api/algorithms/${id}`)
const response = await axios.get(`/api/v1/algorithms/${id}`)
this.currentAlgorithm = response.data
return true
} catch (error: any) {
@@ -106,7 +106,7 @@ export const useAlgorithmStore = defineStore('algorithm', {
this.error = null
try {
const response = await axios.get(`/api/algorithms/${algorithmId}/versions`)
const response = await axios.get(`/api/v1/algorithms/${algorithmId}/versions`)
if (this.currentAlgorithm) {
this.currentAlgorithm.versions = response.data
}
@@ -125,7 +125,7 @@ export const useAlgorithmStore = defineStore('algorithm', {
this.error = null
try {
const response = await axios.post('/api/algorithms/call', request)
const response = await axios.post('/api/v1/algorithms/call', request)
this.callResult = response.data
return true
} catch (error: any) {
@@ -142,7 +142,7 @@ export const useAlgorithmStore = defineStore('algorithm', {
this.error = null
try {
const response = await axios.get(`/api/algorithms/calls/${callId}`)
const response = await axios.get(`/api/v1/algorithms/calls/${callId}`)
this.callResult = response.data
return true
} catch (error: any) {
@@ -159,7 +159,7 @@ export const useAlgorithmStore = defineStore('algorithm', {
this.error = null
try {
const response = await axios.post('/api/openai/generate-data', { prompt, data_type: dataType })
const response = await axios.post('/api/v1/openai/generate-data', { prompt, data_type: dataType })
return response.data
} catch (error: any) {
this.error = error.response?.data?.detail || '生成仿真数据失败'
@@ -175,7 +175,7 @@ export const useAlgorithmStore = defineStore('algorithm', {
this.error = null
try {
const response = await axios.post('/api/algorithms', algorithmData)
const response = await axios.post('/api/v1/algorithms', algorithmData)
return response.data
} catch (error: any) {
this.error = error.response?.data?.detail || '创建算法失败'

View File

@@ -16,40 +16,44 @@
>
<el-menu-item index="/admin/algorithms">
<template #icon>
<el-icon><data-analysis /></el-icon>
<div class="menu-icon algorithms-icon">
<el-icon><data-analysis /></el-icon>
</div>
</template>
<span>算法仓库管理</span>
</el-menu-item>
<el-menu-item index="/admin/services">
<template #icon>
<el-icon><cpu /></el-icon>
<div class="menu-icon services-icon">
<el-icon><cpu /></el-icon>
</div>
</template>
<span>算法服务管理</span>
</el-menu-item>
<el-menu-item index="/admin/api">
<template #icon>
<el-icon><link /></el-icon>
<div class="menu-icon api-icon">
<el-icon><link /></el-icon>
</div>
</template>
<span>API管理</span>
</el-menu-item>
<el-menu-item index="/admin/users">
<template #icon>
<el-icon><user /></el-icon>
<div class="menu-icon users-icon">
<el-icon><user /></el-icon>
</div>
</template>
<span>用户管理</span>
</el-menu-item>
<el-menu-item index="/admin/config">
<template #icon>
<el-icon><setting /></el-icon>
<div class="menu-icon config-icon">
<el-icon><setting /></el-icon>
</div>
</template>
<span>配置管理</span>
</el-menu-item>
<el-menu-item index="/admin/comparison">
<template #icon>
<el-icon><trend-charts /></el-icon>
</template>
<span>算法效果比较</span>
</el-menu-item>
</el-menu>
</aside>
@@ -69,7 +73,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { DataAnalysis, User, Link, Cpu, Setting, TrendCharts } from '@element-plus/icons-vue'
import { DataAnalysis, User, Link, Cpu, Setting } from '@element-plus/icons-vue'
// 获取路由和路由器
const route = useRoute()
@@ -103,14 +107,78 @@ const handleMenuSelect = (key: string) => {
}
.admin-sidebar {
width: 200px;
width: 140px;
min-width: 140px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
padding: 16px 8px;
}
.sidebar-menu {
border-right: none;
background: transparent;
}
.sidebar-menu .el-menu-item {
height: 48px;
line-height: 48px;
margin: 4px 0;
border-radius: 10px;
transition: all 0.3s ease;
}
.sidebar-menu .el-menu-item:hover {
background-color: #f5f7fa;
}
.sidebar-menu .el-menu-item.is-active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
}
.sidebar-menu .el-menu-item.is-active .el-icon {
color: #fff;
}
.sidebar-menu .el-menu-item .el-icon {
font-size: 20px;
margin-right: 12px;
}
.menu-icon {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 8px;
margin-right: 10px;
}
.algorithms-icon {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.services-icon {
background: linear-gradient(135deg, #409eff 0%, #67c23a 100%);
color: white;
}
.api-icon {
background: linear-gradient(135deg, #e6a23c 0%, #f56c6c 100%);
color: white;
}
.users-icon {
background: linear-gradient(135deg, #909399 0%, #c0c4cc 100%);
color: white;
}
.config-icon {
background: linear-gradient(135deg, #f3514e 0%, #f78955 100%);
color: white;
}
.admin-main {

View File

@@ -112,7 +112,7 @@
<el-form-item label="模型文件">
<el-upload
class="model-file-uploader"
action="/api/algorithms/upload-model"
action="/api/v1/algorithms/upload-model"
:headers="{ 'Authorization': `Bearer ${localStorage.getItem('token')}` }"
:on-success="handleModelFileUpload"
:on-error="handleModelFileUploadError"
@@ -555,7 +555,7 @@ const executeCode = async (version: any) => {
}
// 调用后端API执行代码
const response = await fetch('/api/algorithms/execute-code', {
const response = await fetch('/api/v1/algorithms/execute-code', {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@@ -268,7 +268,7 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import axios from 'axios'
@@ -302,6 +302,65 @@ const executionMessage = ref('')
const executionResult = ref<any>(null)
const activeUploadTab = ref('video')
// WebSocket 连接
let wsConnection: WebSocket | null = null
const realtimeDetections = ref<any[]>([])
const isRealtimeMode = ref(false)
// 连接到 WebSocket 服务器
const connectWebSocket = () => {
const wsUrl = `ws://localhost:8766`
try {
wsConnection = new WebSocket(wsUrl)
wsConnection.onopen = () => {
console.log('[WS] Connected to WebSocket server')
isRealtimeMode.value = true
}
wsConnection.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
console.log('[WS] Received:', data)
if (data.msg_type === 'frame') {
realtimeDetections.value.push(data)
// 保持最近100帧
if (realtimeDetections.value.length > 100) {
realtimeDetections.value.shift()
}
} else if (data.msg_type === 'alert') {
ElMessage.warning(`检测到异常: ${data.event_type}`)
}
} catch (e) {
console.error('[WS] Parse error:', e)
}
}
wsConnection.onerror = (error) => {
console.error('[WS] Error:', error)
}
wsConnection.onclose = () => {
console.log('[WS] Disconnected')
isRealtimeMode.value = false
}
} catch (e) {
console.error('[WS] Connection failed:', e)
}
}
// 断开 WebSocket 连接
const disconnectWebSocket = () => {
if (wsConnection) {
wsConnection.close()
wsConnection = null
}
isRealtimeMode.value = false
realtimeDetections.value = []
}
// 分类配置
const categories = {
computer_vision: {
@@ -545,7 +604,9 @@ const executeAlgorithm = async () => {
input_data: {
video: uploadedFile.value.filePath || uploadedFile.value.url
},
params: {}
params: {
ws_url: 'ws://localhost:8766'
}
}
// 调用算法API
@@ -599,6 +660,14 @@ onMounted(async () => {
if (algorithmStore.algorithms.length > 0) {
selectAlgorithm(algorithmStore.algorithms[0])
}
// 连接 WebSocket 服务器
connectWebSocket()
})
// 组件卸载时断开 WebSocket
onUnmounted(() => {
disconnectWebSocket()
})
</script>

View File

@@ -1,619 +0,0 @@
<template>
<div class="algorithm-comparison-container">
<!-- 页面标题 -->
<h1>算法效果比较</h1>
<!-- 比较配置区域 -->
<el-card class="comparison-config-card">
<template #header>
<div class="card-header">
<span>比较配置</span>
</div>
</template>
<!-- 输入数据配置 -->
<el-form :model="comparisonForm" label-width="120px">
<el-form-item label="输入数据">
<el-input
v-model="comparisonForm.input_data"
type="textarea"
:rows="6"
placeholder="请输入测试数据JSON格式"
/>
<div class="hint">
提示输入JSON格式的测试数据例如{"text": "这是一段测试文本"}
</div>
</el-form-item>
<!-- 算法配置列表 -->
<el-form-item label="选择算法">
<div class="algorithm-configs">
<div
v-for="(config, index) in comparisonForm.algorithm_configs"
:key="index"
class="algorithm-config-item"
>
<el-card>
<template #header>
<div class="config-header">
<span>算法 {{ index + 1 }}</span>
<el-button
type="danger"
size="small"
@click="removeAlgorithmConfig(index)"
:disabled="comparisonForm.algorithm_configs.length <= 2"
>
删除
</el-button>
</div>
</template>
<el-form :model="config" label-width="100px">
<el-form-item label="算法ID">
<el-input
v-model="config.algorithm_id"
placeholder="请输入算法ID"
/>
</el-form-item>
<el-form-item label="算法名称">
<el-input
v-model="config.algorithm_name"
placeholder="请输入算法名称"
/>
</el-form-item>
<el-form-item label="版本">
<el-input
v-model="config.version"
placeholder="请输入版本号"
/>
</el-form-item>
<el-form-item label="配置参数">
<el-input
v-model="config.config"
type="textarea"
:rows="3"
placeholder="请输入配置参数JSON格式"
/>
</el-form-item>
</el-form>
</el-card>
</div>
</div>
<el-button
type="primary"
@click="addAlgorithmConfig"
class="add-algorithm-btn"
>
<el-icon><plus /></el-icon>
添加算法
</el-button>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<div class="action-buttons">
<el-button type="primary" @click="compareAlgorithms" :loading="comparing">
<el-icon><loading v-if="comparing" /></el-icon>
开始比较
</el-button>
<el-button @click="resetForm">重置</el-button>
</div>
</el-card>
<!-- 比较结果区域 -->
<el-card v-if="comparisonResult" class="comparison-result-card">
<template #header>
<div class="card-header">
<span>比较结果</span>
<el-button type="success" @click="generateReport">
<el-icon><document /></el-icon>
生成报告
</el-button>
</div>
</template>
<!-- 结果概览 -->
<div class="result-overview">
<el-descriptions :column="3" border>
<el-descriptions-item label="比较时间">
{{ formatDate(comparisonResult.comparison_time) }}
</el-descriptions-item>
<el-descriptions-item label="算法数量">
{{ comparisonResult.results?.length || 0 }}
</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="comparisonResult.success ? 'success' : 'danger'">
{{ comparisonResult.success ? '成功' : '失败' }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 详细结果 -->
<div class="detailed-results">
<h3>详细结果</h3>
<el-table :data="comparisonResult.results" style="width: 100%">
<el-table-column prop="algorithm_name" label="算法名称" width="150" />
<el-table-column prop="algorithm_id" label="算法ID" width="200" />
<el-table-column prop="version" label="版本" width="120" />
<el-table-column prop="execution_time" label="执行时间(ms)" width="120">
<template #default="scope">
{{ scope.row.execution_time?.toFixed(2) || '-' }}
</template>
</el-table-column>
<el-table-column prop="success" label="执行状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.success ? 'success' : 'danger'">
{{ scope.row.success ? '成功' : '失败' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="output" label="输出结果" min-width="200">
<template #default="scope">
<div class="output-cell">
{{ formatOutput(scope.row.output) }}
</div>
</template>
</el-table-column>
<el-table-column prop="error" label="错误信息" width="200">
<template #default="scope">
<span class="error-text">{{ scope.row.error || '-' }}</span>
</template>
</el-table-column>
</el-table>
</div>
<!-- 性能对比图表 -->
<div v-if="comparisonResult.results && comparisonResult.results.length > 0" class="performance-chart">
<h3>性能对比</h3>
<div ref="chartRef" style="width: 100%; height: 400px;"></div>
</div>
</el-card>
<!-- 报告展示区域 -->
<el-card v-if="comparisonReport" class="comparison-report-card">
<template #header>
<div class="card-header">
<span>比较报告</span>
<el-button @click="downloadReport">
<el-icon><download /></el-icon>
下载报告
</el-button>
</div>
</template>
<div class="report-content">
<div class="report-header">
<h2>算法效果比较报告</h2>
<p>生成时间{{ formatDate(comparisonReport.report_time) }}</p>
</div>
<div class="report-section">
<h3>执行摘要</h3>
<p>{{ comparisonReport.summary }}</p>
</div>
<div class="report-section">
<h3>性能分析</h3>
<div v-html="comparisonReport.performance_analysis"></div>
</div>
<div class="report-section">
<h3>建议</h3>
<div v-html="comparisonReport.recommendations"></div>
</div>
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick, onMounted, onUnmounted } from 'vue'
import { Plus, Loading, Document, Download } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import * as echarts from 'echarts'
import { comparisonService, type ComparisonResult, type ComparisonReport, type AlgorithmConfig } from '../../services/admin'
// 状态管理
const comparing = ref(false)
const comparisonResult = ref<any>(null)
const comparisonReport = ref<any>(null)
const chartRef = ref<HTMLElement>()
let chartInstance: echarts.ECharts | null = null
// 比较表单
const comparisonForm = ref({
input_data: '{"text": "这是一段测试文本"}',
algorithm_configs: <AlgorithmConfig[]>[
{
algorithm_id: '',
algorithm_name: '算法1',
version: '1.0.0',
config: '{}'
},
{
algorithm_id: '',
algorithm_name: '算法2',
version: '1.0.0',
config: '{}'
}
]
})
// 添加算法配置
const addAlgorithmConfig = () => {
comparisonForm.value.algorithm_configs.push({
algorithm_id: '',
algorithm_name: `算法${comparisonForm.value.algorithm_configs.length + 1}`,
version: '1.0.0',
config: '{}'
})
}
// 删除算法配置
const removeAlgorithmConfig = (index: number) => {
if (comparisonForm.value.algorithm_configs.length > 2) {
comparisonForm.value.algorithm_configs.splice(index, 1)
} else {
ElMessage.warning('至少需要保留两个算法进行比较')
}
}
// 比较算法
const compareAlgorithms = async () => {
try {
// 验证输入数据
let inputData
try {
inputData = JSON.parse(comparisonForm.value.input_data)
} catch (error) {
ElMessage.error('输入数据格式错误请输入有效的JSON格式')
return
}
// 验证算法配置
const validConfigs = comparisonForm.value.algorithm_configs.filter(config => {
return config.algorithm_id && config.algorithm_name
})
if (validConfigs.length < 2) {
ElMessage.error('至少需要配置两个有效的算法进行比较')
return
}
// 验证配置参数
validConfigs.forEach(config => {
try {
if (config.config) {
JSON.parse(config.config)
}
} catch (error) {
ElMessage.error(`算法 ${config.algorithm_name} 的配置参数格式错误`)
throw error
}
})
comparing.value = true
comparisonResult.value = null
comparisonReport.value = null
comparisonResult.value = await comparisonService.compareAlgorithms(inputData, validConfigs)
if (comparisonResult.value.success) {
ElMessage.success('算法比较完成')
// 渲染图表
nextTick(() => {
renderChart()
})
} else {
ElMessage.error('算法比较失败:' + (comparisonResult.value.error || '未知错误'))
}
} catch (error) {
console.error('算法比较失败:', error)
ElMessage.error('算法比较失败')
} finally {
comparing.value = false
}
}
// 生成报告
const generateReport = async () => {
try {
comparisonReport.value = await comparisonService.generateComparisonReport(comparisonResult.value)
if (comparisonReport.value.success) {
ElMessage.success('报告生成成功')
} else {
ElMessage.error('报告生成失败:' + (comparisonReport.value.error || '未知错误'))
}
} catch (error) {
console.error('生成报告失败:', error)
ElMessage.error('生成报告失败')
}
}
// 渲染图表
const renderChart = () => {
if (!chartRef.value || !comparisonResult.value.results) return
if (chartInstance) {
chartInstance.dispose()
}
chartInstance = echarts.init(chartRef.value)
const results = comparisonResult.value.results
const algorithmNames = results.map((r: any) => r.algorithm_name)
const executionTimes = results.map((r: any) => r.execution_time || 0)
const successRates = results.map((r: any) => r.success ? 100 : 0)
const option = {
title: {
text: '算法性能对比'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['执行时间(ms)', '成功率(%)']
},
xAxis: {
type: 'category',
data: algorithmNames
},
yAxis: [
{
type: 'value',
name: '执行时间(ms)',
position: 'left'
},
{
type: 'value',
name: '成功率(%)',
position: 'right',
max: 100
}
],
series: [
{
name: '执行时间(ms)',
type: 'bar',
data: executionTimes,
yAxisIndex: 0
},
{
name: '成功率(%)',
type: 'line',
data: successRates,
yAxisIndex: 1
}
]
}
chartInstance.setOption(option)
}
// 下载报告
const downloadReport = () => {
if (!comparisonReport.value) return
const reportContent = `
算法效果比较报告
生成时间:${formatDate(comparisonReport.value.report_time)}
执行摘要:
${comparisonReport.value.summary}
性能分析:
${comparisonReport.value.performance_analysis}
建议:
${comparisonReport.value.recommendations}
`
const blob = new Blob([reportContent], { type: 'text/plain;charset=utf-8' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `算法比较报告_${new Date().getTime()}.txt`
link.click()
URL.revokeObjectURL(url)
ElMessage.success('报告下载成功')
}
// 重置表单
const resetForm = () => {
comparisonForm.value = {
input_data: '{"text": "这是一段测试文本"}',
algorithm_configs: <AlgorithmConfig[]>[
{
algorithm_id: '',
algorithm_name: '算法1',
version: '1.0.0',
config: '{}'
},
{
algorithm_id: '',
algorithm_name: '算法2',
version: '1.0.0',
config: '{}'
}
]
}
comparisonResult.value = null
comparisonReport.value = null
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
}
// 格式化日期
const formatDate = (dateString: string) => {
const date = new Date(dateString)
return date.toLocaleString()
}
// 格式化输出
const formatOutput = (output: any) => {
if (typeof output === 'object') {
return JSON.stringify(output).substring(0, 100) + (JSON.stringify(output).length > 100 ? '...' : '')
}
return String(output).substring(0, 100) + (String(output).length > 100 ? '...' : '')
}
// 响应式图表
const handleResize = () => {
if (chartInstance) {
chartInstance.resize()
}
}
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
if (chartInstance) {
chartInstance.dispose()
}
})
</script>
<style scoped>
.algorithm-comparison-container {
width: 100%;
}
.algorithm-comparison-container h1 {
font-size: 24px;
margin-bottom: 20px;
color: #333;
}
.comparison-config-card,
.comparison-result-card,
.comparison-report-card {
margin-bottom: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.hint {
font-size: 12px;
color: #999;
margin-top: 5px;
}
.algorithm-configs {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 15px;
}
.algorithm-config-item {
width: 100%;
}
.config-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.add-algorithm-btn {
width: 100%;
}
.action-buttons {
display: flex;
gap: 10px;
margin-top: 20px;
}
.result-overview {
margin-bottom: 20px;
}
.detailed-results {
margin-bottom: 20px;
}
.detailed-results h3 {
font-size: 18px;
margin-bottom: 15px;
color: #333;
}
.output-cell {
max-height: 60px;
overflow: hidden;
text-overflow: ellipsis;
}
.error-text {
color: #f56c6c;
}
.performance-chart {
margin-top: 20px;
}
.performance-chart h3 {
font-size: 18px;
margin-bottom: 15px;
color: #333;
}
.report-content {
padding: 20px;
}
.report-header {
text-align: center;
margin-bottom: 30px;
}
.report-header h2 {
font-size: 24px;
color: #333;
margin-bottom: 10px;
}
.report-section {
margin-bottom: 30px;
}
.report-section h3 {
font-size: 18px;
color: #333;
margin-bottom: 15px;
border-bottom: 2px solid #409eff;
padding-bottom: 10px;
}
@media (max-width: 768px) {
.algorithm-configs {
gap: 10px;
}
.action-buttons {
flex-direction: column;
}
.card-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
}
</style>

View File

@@ -1,7 +1,12 @@
<template>
<div class="admin-services-container">
<!-- 页面标题 -->
<h1>算法服务管理</h1>
<div class="page-header">
<div class="header-icon">
<el-icon><Cpu /></el-icon>
</div>
<h1>算法服务管理</h1>
</div>
<!-- 操作栏 -->
<div class="action-bar">
@@ -73,12 +78,13 @@
</el-link>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<el-table-column label="操作" width="240">
<template #default="scope">
<el-button size="small" @click="startService(scope.row)" v-if="scope.row.status === 'stopped'">启动</el-button>
<el-button size="small" type="warning" @click="stopService(scope.row)" v-else-if="scope.row.status === 'running'">停止</el-button>
<el-button size="small" @click="restartService(scope.row)">重启</el-button>
<el-button size="small" type="primary" @click="viewServiceDetail(scope.row)">详情</el-button>
<el-button size="small" type="danger" @click="deleteService(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -301,7 +307,7 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { Refresh, View, Plus, InfoFilled } from '@element-plus/icons-vue'
import { Refresh, View, Plus, InfoFilled, Cpu } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
import { serviceManagementApi } from '../../services/serviceManagement'
@@ -470,6 +476,35 @@ const restartService = async (service: any) => {
}
}
// 删除服务
const deleteService = async (service: any) => {
try {
await ElMessageBox.confirm(
`确定要删除服务 "${service.name}" 吗?此操作不可恢复。`,
'删除确认',
{
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
}
)
const result = await serviceManagementApi.deleteService(service.service_id)
if (result.success) {
ElMessage.success('服务删除成功')
await refreshServices()
} else {
ElMessage.error(`服务删除失败: ${result.message}`)
}
} catch (error) {
if (error !== 'cancel') {
console.error('删除服务失败:', error)
ElMessage.error('删除服务失败')
}
}
}
// 查看服务详情
const viewServiceDetail = (service: any) => {
selectedService.value = service
@@ -497,6 +532,34 @@ onMounted(async () => {
padding: 20px;
}
.page-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #e4e7ed;
}
.header-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 12px;
background: linear-gradient(135deg, #409eff 0%, #67c23a 100%);
color: white;
font-size: 24px;
}
.page-header h1 {
margin: 0;
font-size: 24px;
font-weight: 600;
color: #303133;
}
.action-bar {
margin-bottom: 20px;
}

View File

@@ -1,7 +1,12 @@
<template>
<div class="admin-algorithms-container">
<!-- 页面标题 -->
<h1>算法仓库管理</h1>
<div class="page-header">
<div class="header-icon">
<el-icon><Folder /></el-icon>
</div>
<h1>算法仓库管理</h1>
</div>
<!-- 操作栏 -->
<div class="action-bar">
@@ -763,8 +768,12 @@ const loadRepos = async () => {
console.log('Token:', token ? `${token.substring(0, 20)}...` : 'none')
console.log('Token长度:', token?.length)
// 调用后端API获取仓库列表认证头会自动添加
const response = await axios.get('/api/repositories')
// 调用后端API获取仓库列表添加认证头
const response = await axios.get('/api/v1/repositories', {
headers: {
'Authorization': `Bearer ${token}`
}
})
console.log('仓库列表API响应:', response)
console.log('响应状态:', response.status)
@@ -814,7 +823,7 @@ const addRepo = async () => {
const token = localStorage.getItem('token') || ''
// 调用后端API添加仓库
const response = await axios.post('/api/repositories', {
const response = await axios.post('/api/v1/repositories', {
name: repoForm.value.name,
description: repoForm.value.description,
type: repoForm.value.type,
@@ -942,12 +951,10 @@ const editRepo = async (repo: any) => {
console.log('编辑仓库:', repo)
try {
// 导入axios
const axios = await import('axios')
const token = localStorage.getItem('token') || ''
// 调用后端API获取仓库详细信息
const response = await axios.get(`/api/repositories/${repo.id}`, {
const response = await axios.get(`/api/v1/repositories/${repo.id}`, {
headers: {
'Authorization': `Bearer ${token}`
}
@@ -1012,7 +1019,7 @@ const updateRepo = async () => {
})
// 调用后端API更新仓库 - 只更新名称、描述和类型
const response = await axios.put(`/api/repositories/${currentEditingRepo.value.id}`, {
const response = await axios.put(`/api/v1/repositories/${currentEditingRepo.value.id}`, {
name: editRepoForm.value.name,
description: editRepoForm.value.description,
type: editRepoForm.value.type
@@ -1096,11 +1103,10 @@ const deleteRepo = async (repoId: string) => {
})
// 导入axios
const axios = await import('axios')
const token = localStorage.getItem('token') || ''
// 调用后端API删除仓库
const response = await axios.delete(`/api/repositories/${repoId}`, {
const response = await axios.delete(`/api/v1/repositories/${repoId}`, {
headers: {
'Authorization': `Bearer ${token}`
}
@@ -1156,7 +1162,6 @@ const saveGiteaConfig = async () => {
}
// 导入axios
const axios = await import('axios')
const token = localStorage.getItem('token') || ''
// 调用后端API保存配置
@@ -1233,9 +1238,6 @@ const loadGiteaConfig = async () => {
}
try {
// 导入axios
const { default: axios } = await import('axios')
const response = await axios.get('/api/gitea/config', {
headers: {
'Authorization': `Bearer ${token}`
@@ -1273,6 +1275,34 @@ onMounted(async () => {
padding: 20px;
}
.page-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #e4e7ed;
}
.header-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-size: 24px;
}
.page-header h1 {
margin: 0;
font-size: 24px;
font-weight: 600;
color: #303133;
}
.action-bar {
margin-bottom: 20px;
}

View File

@@ -1,7 +1,12 @@
<template>
<div class="admin-api-management-container">
<!-- 页面标题 -->
<h1>API管理</h1>
<div class="page-header">
<div class="header-icon">
<el-icon><Link /></el-icon>
</div>
<h1>API管理</h1>
</div>
<!-- 操作栏 -->
<div class="action-bar">
@@ -13,6 +18,10 @@
<el-icon><Refresh /></el-icon>
刷新
</el-button>
<el-button type="success" @click="syncApiEndpoints">
<el-icon><Connection /></el-icon>
同步服务
</el-button>
<el-button type="info" @click="showHelpDialog = true">
<el-icon><InfoFilled /></el-icon>
帮助说明
@@ -319,7 +328,7 @@
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAlgorithmStore } from '../../stores/algorithm'
import { Plus, Refresh, InfoFilled } from '@element-plus/icons-vue'
import { Plus, Refresh, InfoFilled, Connection, Link } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { apiManagementService, type ApiEndpoint } from '../../services/apiManagement'
@@ -349,7 +358,9 @@ const apiForm = ref({
version_id: '',
is_public: false,
requires_auth: true,
description: ''
description: '',
allowed_roles: [] as string[],
config: {} as Record<string, any>
})
const testForm = ref({
@@ -444,6 +455,23 @@ const loadApiStats = async () => {
}
}
// 同步服务到API端点
const syncApiEndpoints = async () => {
try {
const result = await apiManagementService.syncApiEndpoints()
if (result.success) {
ElMessage.success(result.message)
await loadApis()
await loadApiStats()
} else {
ElMessage.error(result.message || '同步失败')
}
} catch (error: any) {
console.error('同步API端点失败:', error)
ElMessage.error(error.message || '同步失败')
}
}
// 加载算法列表
const loadAlgorithms = async () => {
try {
@@ -501,7 +529,9 @@ const editApi = (api: ApiEndpoint) => {
version_id: api.version_id,
is_public: api.is_public,
requires_auth: true,
description: api.description
description: api.description,
allowed_roles: [],
config: {}
}
showAddDialog.value = true
}
@@ -597,7 +627,9 @@ const closeDialog = () => {
version_id: '',
is_public: false,
requires_auth: true,
description: ''
description: '',
allowed_roles: [],
config: {}
}
if (apiFormRef.value) {
apiFormRef.value.resetFields()
@@ -617,10 +649,32 @@ onMounted(async () => {
width: 100%;
}
.admin-api-management-container h1 {
.page-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #e4e7ed;
}
.header-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 12px;
background: linear-gradient(135deg, #e6a23c 0%, #f56c6c 100%);
color: white;
font-size: 24px;
margin-bottom: 20px;
color: #333;
}
.page-header h1 {
margin: 0;
font-size: 24px;
font-weight: 600;
color: #303133;
}
.action-bar {

View File

@@ -1,7 +1,12 @@
<template>
<div class="config-management-container">
<!-- 页面标题 -->
<h1>配置管理</h1>
<div class="page-header">
<div class="header-icon">
<el-icon><Setting /></el-icon>
</div>
<h1>配置管理</h1>
</div>
<!-- 操作栏 -->
<div class="action-bar">
@@ -285,7 +290,7 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Plus, InfoFilled } from '@element-plus/icons-vue'
import { Plus, InfoFilled, Setting } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { configService, type ConfigItem } from '../../services/admin'
@@ -537,10 +542,32 @@ onMounted(() => {
width: 100%;
}
.config-management-container h1 {
.page-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #e4e7ed;
}
.header-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 12px;
background: linear-gradient(135deg, #f3514e 0%, #f78955 100%);
color: white;
font-size: 24px;
margin-bottom: 20px;
color: #333;
}
.page-header h1 {
margin: 0;
font-size: 24px;
font-weight: 600;
color: #303133;
}
.action-bar {

View File

@@ -1,6 +1,12 @@
<template>
<div class="admin-users">
<h2>用户管理</h2>
<!-- 页面标题 -->
<div class="page-header">
<div class="header-icon">
<el-icon><User /></el-icon>
</div>
<h2>用户管理</h2>
</div>
<div class="users-actions">
<el-button type="primary" @click="showCreateDialog">创建用户</el-button>
</div>
@@ -89,6 +95,7 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from 'axios'
import { User } from '@element-plus/icons-vue'
const users = ref<any[]>([])
const loading = ref(false)
@@ -271,10 +278,32 @@ onMounted(async () => {
padding: 20px;
}
.admin-users h2 {
font-size: 20px;
margin-bottom: 20px;
color: #333;
.page-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #e4e7ed;
}
.header-icon {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 12px;
background: linear-gradient(135deg, #909399 0%, #c0c4cc 100%);
color: white;
font-size: 24px;
}
.page-header h2 {
margin: 0;
font-size: 24px;
font-weight: 600;
color: #303133;
}
.users-actions {