更新前端项目

This commit is contained in:
2025-11-17 16:49:03 +08:00
parent 638e152cff
commit 033890742d
16 changed files with 862 additions and 130 deletions

View File

@@ -9,7 +9,7 @@ export const checkEnvironmentVariables = () => {
// 在Vite中环境变量可能通过define选项直接定义
// 或者通过import.meta.env读取
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || 'http://hertzServer:8000/api'
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000'
const appTitle = import.meta.env.VITE_APP_TITLE || 'Hertz Admin'
const appVersion = import.meta.env.VITE_APP_VERSION || '1.0.0'
@@ -30,7 +30,7 @@ export const checkEnvironmentVariables = () => {
// 检查可选的环境变量
const devServerHost = import.meta.env.VITE_DEV_SERVER_HOST || 'localhost'
const devServerPort = import.meta.env.VITE_DEV_SERVER_PORT || '3001'
const devServerPort = import.meta.env.VITE_DEV_SERVER_PORT || '3000'
const optionalVars = [
{ key: 'VITE_DEV_SERVER_HOST', value: devServerHost },
@@ -72,7 +72,7 @@ export const validateEnvironment = () => {
// 获取API基础地址
export const getApiBaseUrl = (): string => {
return import.meta.env.VITE_API_BASE_URL || 'http://localhost:3001/api'
return import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000'
}
// 获取应用配置
@@ -82,6 +82,6 @@ export const getAppConfig = () => {
version: import.meta.env.VITE_APP_VERSION || '1.0.0',
apiBaseUrl: getApiBaseUrl(),
devServerHost: import.meta.env.VITE_DEV_SERVER_HOST || 'localhost',
devServerPort: import.meta.env.VITE_DEV_SERVER_PORT || '3001',
devServerPort: import.meta.env.VITE_DEV_SERVER_PORT || '3000',
}
}

View File

@@ -122,16 +122,16 @@ export const showRoutesInfo = () => {
console.log(' 💡 提示: 可以在浏览器中直接访问这些路径')
console.log('\n🌐 可用链接:')
console.log(' http://localhost:3001/ - 首页 (需要登录)')
console.log(' http://localhost:3001/login - 登录页面')
console.log(' http://localhost:3001/dashboard - 仪表板 (需要登录)')
console.log(' http://localhost:3001/user - 用户管理 (需要登录)')
console.log(' http://localhost:3001/profile - 个人资料 (需要登录)')
console.log(' http://localhost:3001/settings - 系统设置 (需要登录)')
console.log(' http://localhost:3001/test - 样式测试 (公开)')
console.log(' http://localhost:3001/websocket-test - WebSocket测试 (公开)')
console.log(' http://localhost:3001/demo - 动态路由演示 (公开)')
console.log(' http://localhost:3001/any-other-path - 404页面 (公开)')
console.log(' http://localhost:3000/ - 首页 (需要登录)')
console.log(' http://localhost:3000/login - 登录页面')
console.log(' http://localhost:3000/dashboard - 仪表板 (需要登录)')
console.log(' http://localhost:3000/user - 用户管理 (需要登录)')
console.log(' http://localhost:3000/profile - 个人资料 (需要登录)')
console.log(' http://localhost:3000/settings - 系统设置 (需要登录)')
console.log(' http://localhost:3000/test - 样式测试 (公开)')
console.log(' http://localhost:3000/websocket-test - WebSocket测试 (公开)')
console.log(' http://localhost:3000/demo - 动态路由演示 (公开)')
console.log(' http://localhost:3000/any-other-path - 404页面 (公开)')
console.log('\n✅ 路由配置加载完成!')
console.log('💡 提示: 启动项目后会在控制台看到真正的动态路由信息')

View File

@@ -24,10 +24,25 @@ export function getFullFileUrl(relativePath: string): string {
}
// 在生产环境中拼接完整的URL
const baseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3001'
const baseURL = getBackendBaseUrl()
return `${baseURL}${relativePath}`
}
export function getBackendBaseUrl(): string {
return import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000'
}
export function getWsBaseUrl(): string {
const httpBase = getBackendBaseUrl()
if (httpBase.startsWith('https://')) {
return 'wss://' + httpBase.slice('https://'.length)
}
if (httpBase.startsWith('http://')) {
return 'ws://' + httpBase.slice('http://'.length)
}
return httpBase
}
/**
* 获取API基础URL
* @returns API基础URL
@@ -36,7 +51,7 @@ export function getApiBaseUrl(): string {
if (import.meta.env.DEV) {
return '' // 开发环境使用空字符串通过Vite代理
}
return import.meta.env.VITE_API_BASE_URL || 'http://localhost:3001'
return getBackendBaseUrl()
}
/**
@@ -47,7 +62,7 @@ export function getMediaBaseUrl(): string {
if (import.meta.env.DEV) {
return '' // 开发环境使用空字符串通过Vite代理
}
const baseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3001'
const baseURL = getBackendBaseUrl()
return baseURL.replace('/api', '') // 移除/api后缀
}

View File

@@ -17,6 +17,13 @@
</template>
刷新
</a-button>
<a-button
type="primary"
:disabled="!selectedRowKeys.length"
@click="openBatchEdit"
>
批量修改等级
</a-button>
</div>
<div class="action-right">
<a-input-search
@@ -35,6 +42,7 @@
:loading="loading"
:pagination="pagination"
row-key="id"
:row-selection="rowSelection"
class="levels-table"
>
<template #bodyCell="{ column, record }">
@@ -180,6 +188,41 @@
</a-form>
</div>
</a-modal>
<!-- 批量切换警告等级弹窗 -->
<a-modal
v-model:visible="batchEditModalVisible"
title="批量修改警告等级"
@ok="handleBatchEdit"
@cancel="handleBatchEditCancel"
width="500px"
>
<div class="alert-level-edit">
<p style="margin-bottom: 12px;">
已选择 <strong>{{ selectedRowKeys.length }}</strong> 个类别将统一修改为新的警告等级
</p>
<a-form
ref="batchEditFormRef"
:model="batchEditForm"
:rules="editRules"
layout="vertical"
style="margin-top: 12px"
>
<a-form-item label="新的警告等级" name="alert_level">
<a-select
v-model:value="batchEditForm.alert_level"
placeholder="请选择新的警告等级"
style="width: 100%"
>
<a-select-option value="low"></a-select-option>
<a-select-option value="medium"></a-select-option>
<a-select-option value="high"></a-select-option>
</a-select>
</a-form-item>
</a-form>
</div>
</a-modal>
</div>
</template>
@@ -193,7 +236,7 @@ import {
EditOutlined,
PoweroffOutlined
} from '@ant-design/icons-vue'
import { yoloApi, type AlertLevel } from '@/api/yolo'
import { yoloApi, type AlertLevel, type YoloModel } from '@/api/yolo'
import dayjs from 'dayjs'
// 响应式数据
@@ -201,19 +244,36 @@ const loading = ref(false)
const editing = ref(false)
const levels = ref<AlertLevel[]>([])
const searchKeyword = ref('')
const currentModel = ref<YoloModel | null>(null)
const selectedRowKeys = ref<number[]>([])
// 表格多选配置
const rowSelection = computed(() => ({
selectedRowKeys: selectedRowKeys.value,
onChange: (keys: (string | number)[]) => {
selectedRowKeys.value = keys as number[]
}
}))
// 弹窗状态
const detailModalVisible = ref(false)
const editModalVisible = ref(false)
const currentLevel = ref<AlertLevel | null>(null)
const batchEditModalVisible = ref(false)
// 编辑表单
const editForm = reactive({
alert_level: 'low' as 'low' | 'medium' | 'high'
})
// 批量编辑表单
const batchEditForm = reactive({
alert_level: 'low' as 'low' | 'medium' | 'high'
})
// 表单引用
const editFormRef = ref()
const batchEditFormRef = ref()
// 表单验证规则
const editRules = {
@@ -222,14 +282,22 @@ const editRules = {
]
}
// 分页配置
// 分页配置(受控分页)
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `${total} 条记录`
showTotal: (total: number) => `${total} 条记录`,
onChange: (page: number, pageSize: number) => {
pagination.current = page
pagination.pageSize = pageSize
},
onShowSizeChange: (page: number, pageSize: number) => {
pagination.current = page
pagination.pageSize = pageSize
}
})
// 表格列配置
@@ -293,18 +361,45 @@ const getAlertLevelColor = (level: string) => {
return colorMap[level] || 'default'
}
// 获取警告等级列表
// 获取警告等级列表(仅显示当前启用模型的类别)
const fetchLevels = async () => {
try {
loading.value = true
console.log('🔍 开始获取警告等级列表...')
// 先获取当前启用的模型
let enabledModelId: string | null = null
try {
const modelResp = await yoloApi.getCurrentEnabledModel()
console.log('📋 当前启用模型API响应:', modelResp)
if (modelResp.success && modelResp.data) {
currentModel.value = modelResp.data
enabledModelId = modelResp.data.id
} else {
currentModel.value = null
}
} catch (e) {
console.error('❌ 获取当前启用模型失败:', e)
currentModel.value = null
}
// 再获取所有类别
const response = await yoloApi.getAlertLevels()
console.log('📋 警告等级API响应:', response)
if (response.success && response.data) {
levels.value = response.data
pagination.total = response.data.length
let data = response.data
// 如果有启用模型,则按模型过滤类别
if (enabledModelId) {
const modelIdNum = Number(enabledModelId)
data = data.filter(level => level.model === modelIdNum)
console.log('✅ 过滤后类别列表:', data)
}
levels.value = data
pagination.total = data.length
pagination.current = 1
console.log('✅ 警告等级获取成功:', levels.value)
} else {
console.error('❌ 获取警告等级失败:', response.message)
@@ -400,28 +495,28 @@ const handleEdit = async () => {
if (!currentLevel.value) return
editing.value = true
console.log('🔍 开始切换警告等级...', currentLevel.value.id, editForm)
console.log('开始切换警告等级...', currentLevel.value.id, editForm)
// 构造更新数据 - 只传递 alert_level
const updateData = {
alert_level: editForm.alert_level
}
console.log('📤 发送更新数据:', updateData)
console.log('发送更新数据:', updateData)
const response = await yoloApi.updateAlertLevel(currentLevel.value.id.toString(), updateData)
console.log('📋 更新警告等级API响应:', response)
console.log('更新警告等级API响应:', response)
if (response.success) {
message.success(`警告等级已切换为: ${editForm.alert_level}`)
editModalVisible.value = false
fetchLevels()
} else {
console.error('切换警告等级失败:', response.message)
console.error('切换警告等级失败:', response.message)
message.error(response.message || '切换失败')
}
} catch (error) {
console.error('切换警告等级异常:', error)
console.error('切换警告等级异常:', error)
message.error('切换失败')
} finally {
editing.value = false
@@ -434,13 +529,78 @@ const handleEditCancel = () => {
currentLevel.value = null
}
// 打开批量编辑弹窗
const openBatchEdit = () => {
if (!selectedRowKeys.value.length) {
message.warning('请先选择要修改的类别')
return
}
batchEditForm.alert_level = 'low'
batchEditModalVisible.value = true
}
// 批量修改警告等级
const handleBatchEdit = async () => {
try {
if (batchEditFormRef.value && batchEditFormRef.value.validate) {
await batchEditFormRef.value.validate()
}
if (!selectedRowKeys.value.length) {
message.warning('请先选择要修改的类别')
return
}
editing.value = true
const targetLevel = batchEditForm.alert_level
let successCount = 0
let failCount = 0
for (const id of selectedRowKeys.value) {
try {
const response = await yoloApi.updateAlertLevel(id.toString(), {
alert_level: targetLevel
})
if (response.success) {
successCount++
} else {
failCount++
}
} catch (error) {
console.error('批量切换警告等级异常:', error)
failCount++
}
}
if (successCount > 0) {
message.success(`成功更新 ${successCount} 条警告等级`)
}
if (failCount > 0) {
message.error(`${failCount} 条更新失败,请检查日志`)
}
batchEditModalVisible.value = false
selectedRowKeys.value = []
fetchLevels()
} catch (error) {
console.error('批量切换警告等级失败:', error)
} finally {
editing.value = false
}
}
// 取消批量编辑
const handleBatchEditCancel = () => {
batchEditModalVisible.value = false
}
// 切换状态
const toggleStatus = async (level: AlertLevel) => {
try {
console.log('🔍 开始切换警告等级状态...', level.id)
console.log('开始切换警告等级状态...', level.id)
const response = await yoloApi.toggleAlertLevelStatus(level.id.toString())
console.log('📋 切换状态API响应:', response)
console.log('切换状态API响应:', response)
if (response.success) {
message.success(level.is_active ? '警告等级已禁用' : '警告等级已启用')
@@ -457,7 +617,9 @@ const toggleStatus = async (level: AlertLevel) => {
// 搜索处理
const handleSearch = () => {
// 搜索逻辑已在computed中处理
// 搜索逻辑已在computed中处理,这里只负责重置分页
pagination.current = 1
pagination.total = filteredLevels.value.length
}
// 组件挂载时获取数据

View File

@@ -332,6 +332,7 @@ import {
} from '@ant-design/icons-vue'
import { yoloApi, type DetectionHistoryRecord, type YoloModel } from '@/api/yolo'
import dayjs from 'dayjs'
import { getFullFileUrl } from '@/utils/hertz_url'
// 响应式数据
const loading = ref(false)
@@ -467,8 +468,7 @@ const formatDate = (dateString: string) => {
// 获取图片URL
const getImageUrl = (filePath: string) => {
if (!filePath) return ''
if (filePath.startsWith('http')) return filePath
return `http://localhost:3001${filePath}`
return getFullFileUrl(filePath)
}
// 处理图片加载错误

View File

@@ -468,7 +468,7 @@ import {
UploadOutlined,
ReloadOutlined
} from '@ant-design/icons-vue'
import { getFullFileUrl } from '@/utils/hertz_url'
import { getFullFileUrl, getWsBaseUrl, getBackendBaseUrl } from '@/utils/hertz_url'
import { yoloDetector, type YOLODetectionResult } from '@/utils/yolo_frontend'
import { yoloApi } from '@/api/yolo'
@@ -477,7 +477,7 @@ const inferenceMode = ref<'pt' | 'onnx'>('pt')
// WebSocket连接PT推理模式使用
let ws: WebSocket | null = null
const wsUrl = 'ws://localhost:8000/ws/yolo/live/'
const wsUrl = `${getWsBaseUrl()}/ws/yolo/live/`
// 响应式数据
const loading = ref(false)
@@ -1342,7 +1342,7 @@ const handleOnnxUpload = async () => {
// 如果URL不是完整路径构建完整URL
if (!labelsDownloadUrl.startsWith('http')) {
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3001'
const baseUrl = getBackendBaseUrl()
labelsDownloadUrl = labelsDownloadUrl.startsWith('/')
? `${baseUrl}${labelsDownloadUrl}`
: `${baseUrl}/${labelsDownloadUrl}`
@@ -1417,7 +1417,7 @@ const handleOnnxUpload = async () => {
// 如果URL不是完整路径构建完整URL
if (!downloadUrl.startsWith('http')) {
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3001'
const baseUrl = getBackendBaseUrl()
downloadUrl = downloadUrl.startsWith('/')
? `${baseUrl}${downloadUrl}`
: `${baseUrl}/${downloadUrl}`

View File

@@ -918,6 +918,7 @@ import {
} from '@ant-design/icons-vue'
import { yoloApi, detectionHistoryApi, type YoloDetection, type YoloModel } from '@/api/yolo'
import { useUserStore } from '@/stores/hertz_user'
import { getFullFileUrl } from '@/utils/hertz_url'
// 布局模式
const layoutMode = ref<'classic' | 'vertical' | 'grid'>('classic')
@@ -1141,25 +1142,14 @@ const startDetection = async () => {
console.log('检测API响应:', response)
if (response.data) {
// 构建完整的图片URL
const baseUrl = 'http://localhost:3001'
// 构建完整的图片URL(统一通过工具函数处理)
console.log('🔍 原始URL数据:', {
original_file_url: response.data.original_file_url,
result_file_url: response.data.result_file_url
})
// 确保URL路径正确
let originalImageUrl = response.data.original_file_url
let resultImageUrl = response.data.result_file_url
// 如果URL不是以http开头则添加baseUrl
if (!originalImageUrl.startsWith('http')) {
originalImageUrl = `${baseUrl}${originalImageUrl}`
}
if (!resultImageUrl.startsWith('http')) {
resultImageUrl = `${baseUrl}${resultImageUrl}`
}
let originalImageUrl = getFullFileUrl(response.data.original_file_url)
let resultImageUrl = getFullFileUrl(response.data.result_file_url)
console.log('🖼️ 构建后的图片URL:', {
original: originalImageUrl,

View File

@@ -4362,6 +4362,11 @@ const handleLogout = () => {
flex: 1;
border-bottom: none;
line-height: 62px;
background: transparent;
:deep(.ant-menu) {
background: transparent;
}
:deep(.ant-menu-item),
:deep(.ant-menu-submenu) {