final version
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 || '创建算法失败'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user