更新前端项目
This commit is contained in:
@@ -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',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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('💡 提示: 启动项目后会在控制台看到真正的动态路由信息')
|
||||
|
||||
@@ -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后缀
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
// 处理图片加载错误
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user