<template>
  <ContentWrap>
    <!-- 搜索工作栏 -->
    <el-form
      class="-mb-15px"
      :model="queryParams"
      ref="queryFormRef"
      :inline="true"
      label-width="80px"
    >
      <el-form-item label="训练名称" prop="name">
        <el-input
          v-model="queryParams.name"
          placeholder="请输入训练名称"
          clearable
          @keyup.enter="handleQuery"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item label="数据集" prop="dataId">
        <el-select
          v-model="queryParams.dataId"
          placeholder="请选择数据集"
          clearable
          class="!w-240px"
        >
          <el-option
            v-for="project in projectList"
            :key="project.id"
            :label="project.name"
            :value="project.id"
          />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
        <el-button
          type="primary"
          plain
          @click="openForm('create')"
          v-hasPermi="['annotation:train:create']"
        >
          <Icon icon="ep:plus" class="mr-5px" /> 新增
        </el-button>
        <el-button
          type="success"
          plain
          @click="handleExport"
          :loading="exportLoading"
          v-hasPermi="['annotation:train:export']"
        >
          <Icon icon="ep:search" class="mr-5px" /> 导出
        </el-button>
         <el-button
          type="primary"
          plain
          @click="environmentInquiry()"
          v-hasPermi="['annotation:train:create']"
        >
          <Icon icon="ep:plus" class="mr-5px" /> 环境查询
        </el-button>
      </el-form-item>
    </el-form>
  </ContentWrap>

  <!-- 列表 -->
  <ContentWrap>
    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
      <el-table-column label="训练名称" align="center" prop="name" />
      <el-table-column label="数据集" align="center" prop="dataId">
        <template #default="scope">
          {{ getProjectName(scope.row.dataId) }}
        </template>
      </el-table-column>
      <el-table-column label="训练类型" align="center" prop="trainType">
        <template #default="scope">
          <dict-tag :type="DICT_TYPE.TRAINING_STATUS" :value="scope.row.trainType" />
        </template>
      </el-table-column>
      <el-table-column label="识别类型" align="center" prop="visualType">
        <template #default="scope">
          <dict-tag :type="DICT_TYPE.VISUAL_TYPE" :value="getProjectType(scope.row.dataId)" />
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" width="300">
        <template #default="scope">
          <el-button
            link
            type="primary"
            @click="openForm('update', scope.row.id)"
            v-hasPermi="['annotation:train:update']"
          >
            修改
          </el-button>
         
          <el-button
            link
            type="success"
            @click="initTrain(scope.row)"
          >
            初始化
          </el-button>
          <el-button
            link
            type="success"
            @click="openTrainDrawer(scope.row)"
          >
            训练
          </el-button>
          <el-button
            link
            type="success"
            v-if="scope.row.trainType === 4"
            @click="handleInfo(scope.row)"
          >
            测试
          </el-button>
          <el-button
            link
            type="danger"
            @click="handleDelete(scope.row.id)"
            v-hasPermi="['annotation:train:delete']"
          >
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页 -->
    <Pagination
      :total="total"
      v-model:page="queryParams.pageNo"
      v-model:limit="queryParams.pageSize"
      @pagination="getList"
    />
  </ContentWrap>

  <!-- 表单弹窗：添加/修改 -->
  <TrainForm ref="formRef" @success="getList" />

  <!-- 训练抽屉 -->
  <el-drawer
    v-model="trainDrawerVisible"
    title="训练详情"
    direction="rtl"
    size="50%"
    :before-close="closeTrainDrawer"
  >
    <div class="train-drawer-content">
      <!-- 训练信息显示区域 -->
      <div class="train-info">
        <h3>当前训练: {{ currentTrain?.name }}</h3>
        <div v-if="trainData" class="train-stats">
          <div class="stat-item">
            <span class="stat-label">训练状态:</span>
            <el-tag :type="getStatusType(getTrainingStatus())">
              {{ getTrainingStatus() }}
            </el-tag>
          </div>
          <div class="stat-item" v-if="trainData.round !== undefined && trainData.roundTotal !== undefined">
            <span class="stat-label">当前轮次:</span>
            <span class="stat-value">{{ trainData.round }} / {{ trainData.roundTotal }}</span>
          </div>
          <div class="stat-item" v-if="trainData.round !== undefined && trainData.roundTotal !== undefined">
            <span class="stat-label">训练进度:</span>
            <el-progress 
              :percentage="trainProgress" 
              :status="trainData.round >= trainData.roundTotal ? 'success' : ''"
            />
          </div>
          <div class="stat-item" v-if="getMetricsValue('loss')">
            <span class="stat-label">损失值:</span>
            <span class="stat-value">{{ getMetricsValue('loss') }}</span>
          </div>
          <div class="stat-item" v-if="getMetricsValue('precision')">
            <span class="stat-label">精度:</span>
            <span class="stat-value">{{ (parseFloat(getMetricsValue('precision')) * 100).toFixed(2) + '%' }}</span>
          </div>
          <div class="stat-item" v-if="getMetricsValue('recall')">
            <span class="stat-label">召回率:</span>
            <span class="stat-value">{{ (parseFloat(getMetricsValue('recall')) * 100).toFixed(2) + '%' }}</span>
          </div>
          <div class="stat-item" v-if="getMetricsValue('map')">
            <span class="stat-label">mAP:</span>
            <span class="stat-value">{{ (parseFloat(getMetricsValue('map')) * 100).toFixed(2) + '%' }}</span>
          </div>
          <div class="stat-item" v-if="trainData.path">
            <span class="stat-label">输出路径:</span>
            <span class="stat-value">{{ trainData.path }}</span>
          </div>
        </div>
        <div v-else class="loading-placeholder">
          <el-skeleton :rows="5" animated />
        </div>
      </div>

      <!-- 日志显示区域 -->
      <div class="train-logs">
        <h4>训练日志</h4>
        <el-scrollbar height="100%" class="log-container">
          <div v-if="trainLogs.length > 0" class="log-content">
            <div v-for="(log, index) in trainLogs" :key="index" class="log-item">
              <!-- <span class="log-time">{{ formatTime(log.timestamp) }}</span> -->
              <span class="log-message">{{ log.message }}</span>
            </div>
          </div>
          <div v-else class="empty-logs">
            <el-empty description="暂无日志" />
          </div>
        </el-scrollbar>
      </div>
    </div>

    <!-- 底部按钮区域 -->
    <template #footer>
      <div class="drawer-footer">
        <el-button 
          type="primary" 
          @click="startTraining"
          :disabled="isTraining"
          :loading="isTraining"
        >
          {{ isTraining ? '训练中...' : '开始训练' }}
        </el-button>
        <el-button 
          @click="exportTrainData"
          :disabled="!hasTrainResult"
        >
          <Icon icon="ep:download" class="mr-5px" /> 导出
        </el-button>
        <el-button @click="closeTrainDrawer">关闭</el-button>
      </div>
    </template>
  </el-drawer>

  <!-- 测试对话框 -->
  <Dialog 
    v-model="testDialogVisible" 
    title="测试识别" 
    width="60%"
    height="50%"
  >
    <div class="test-dialog-content">
      <!-- 左侧识别结果 -->
      <div class="test-left">
        <h4>识别结果</h4>
        <div class="result-container">
          <div v-if="selectedTrainImage" class="result-content">
            <img 
              :src="selectedTrainImage" 
              class="result-image"
              alt="测试图片"
            />
            <!-- <div class="result-data">
              <pre>{{ JSON.stringify(testResult, null, 2) }}</pre>
            </div> -->
          </div>
          <div v-else class="result-placeholder">
            <el-empty description="请选择图片进行识别" />
          </div>
        </div>
      </div>
      
      <!-- 右侧图片列表 -->
      <div class="test-right">
        <div class="test-right-header">
          <h4>图片列表</h4>
          <div class="test-right-buttons">
            <el-button 
              type="danger" 
              size="small" 
              @click="clearImageList"
              :disabled="imageList.length === 0"
            >
              清空
            </el-button>
            <el-button 
              type="primary" 
              size="small" 
              @click="showAddImageDialog"
            >
              新增
            </el-button>
          </div>
        </div>
        <div class="image-list-container">
          <el-scrollbar height="400px">
            <div class="image-list">
              <div 
                v-for="(image, index) in imageList" 
                :key="index"
                :class="['image-item', { 'selected': selectedImage === image }]"
                @click="selectImage(image)"
              >
                <img 
                  :src="image" 
                  class="thumbnail"
                  :alt="`图片 ${index + 1}`"
                  v-if="image"
                />
                <div v-else class="empty-image">图片 {{ index + 1 }}</div>
              </div>
            </div>
          </el-scrollbar>
        </div>
      </div>
    </div>
    
    <template #footer>
      <el-button @click="testDialogVisible = false">关闭</el-button>
    </template>
  </Dialog>

  <!-- 新增图片对话框 -->
  <Dialog 
    v-model="addImageDialogVisible" 
    title="新增测试图片" 
    width="500px"
  >
    <div class="upload-container">
      <el-upload
        ref="uploadRef"
        :auto-upload="false"
        :show-file-list="true"
        :on-change="handleFileChange"
        :on-remove="handleFileRemove"
        :before-upload="beforeUpload"
        multiple
        accept="image/*"
        drag
        class="upload-dragger"
      >
        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
        <div class="el-upload__text">
          将文件拖到此处，或<em>点击上传</em>
        </div>
        <template #tip>
          <div class="el-upload__tip">
            支持上传jpg/png/gif/bmp/webp格式的图片文件，可同时选择多个文件
          </div>
        </template>
      </el-upload>
    </div>
    
    <template #footer>
      <el-button @click="cancelUpload">取消</el-button>
      <el-button 
        type="primary" 
        @click="uploadImages"
        :loading="addImageLoading"
        :disabled="uploadFiles.length === 0"
      >
        确认上传 ({{ uploadFiles.length }}个文件)
      </el-button>
    </template>
  </Dialog>
</template>

<script setup lang="ts">
import download from '@/utils/download'
import { TrainApi, TrainVO } from '@/api/annotation/train'
import { MarkApi, DataVO } from '@/api/annotation/mark'
import TrainForm from './TrainForm.vue'
import { DICT_TYPE } from '@/utils/dict'
import { UploadFilled } from '@element-plus/icons-vue'

/** 训练 列表 */

/** 训练 列表 */
defineOptions({ name: 'Train' })

const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化

const loading = ref(true) // 列表的加载中
const list = ref<TrainVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const projectList = ref<DataVO[]>([]) // 项目列表
const selectedTrainImage = ref('')
const queryParams = reactive({
  pageNo: 1,
  pageSize: 10,
  name: undefined,
  dataId: undefined,
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中

// 训练抽屉相关
const trainDrawerVisible = ref(false)
const currentTrain = ref<TrainVO | null>(null)
const trainData = ref<any>(null)
const trainLogs = ref<Array<{timestamp: number, message: string}>>([])
const isTraining = ref(false)
const trainProgress = ref(0)
let trainTimer: NodeJS.Timeout | null = null

// 测试相关状态
const testDialogVisible = ref(false) // 测试对话框显示状态
const testLoading = ref(false) // 测试加载状态
const imageList = ref<string[]>([]) // 图片列表
const selectedImage = ref<string>('') // 选中的图片
const testResult = ref<any>(null) // 识别结果
const currentTestTrain = ref<TrainVO | null>(null) // 当前测试的训练

// 新增图片相关状态
const addImageDialogVisible = ref(false) // 新增图片对话框显示状态
const addImageLoading = ref(false) // 新增图片加载状态
const uploadFiles = ref<File[]>([]) // 上传的文件列表
const uploadRef = ref() // 上传组件引用

// 获取项目列表
const getProjectList = async () => {
  try {
    // 调用实际API获取项目列表
    const response = await MarkApi.getProjectStatusList({status:2})
    projectList.value = response
    console.log(projectList.value)
  } catch (error) {
    console.error('获取项目列表失败:', error)
  }
}

// 获取项目名称
const getProjectName = (dataId: number) => {
  const project = projectList.value.find(p => p.id === dataId)
  return project ? project.name : '未知项目'
}
// 获取项目状态
const getProjectStatus = (dataId: number) => {
  const project = projectList.value.find(p => p.id === dataId)
  return project ? project.status : ''
}

// 获取项目类型
const getProjectType = (dataId: number) => {
  const project = projectList.value.find(p => p.id === dataId)
  return project ? project.type : ''
}

/** 查询列表 */
const getList = async () => {
  loading.value = true
  try {
    const data = await TrainApi.getTrainPage(queryParams)
    list.value = data.list
    total.value = data.total
  } finally {
    loading.value = false
  }
}

/** 搜索按钮操作 */
const handleQuery = () => {
  queryParams.pageNo = 1
  getList()
}

/** 重置按钮操作 */
const resetQuery = () => {
  queryFormRef.value.resetFields()
  handleQuery()
}

/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
  formRef.value.open(type, id)
}

/** 打开训练抽屉 */
const openTrainDrawer = (row: TrainVO) => {
  currentTrain.value = row
  trainDrawerVisible.value = true
  startPolling()
}

/** 关闭训练抽屉 */
const closeTrainDrawer = () => {
  trainDrawerVisible.value = false
  stopPolling()
  currentTrain.value = null
  trainData.value = null
  trainLogs.value = []
  trainProgress.value = 0
  isTraining.value = false
}

/** 开始轮询训练数据 */
const startPolling = () => {
  // 立即获取一次数据
  fetchTrainData()
  
  // 每5秒获取一次数据
  trainTimer = setInterval(() => {
    fetchTrainData()
  }, 5000)
}

/** 停止轮询 */
const stopPolling = () => {
  if (trainTimer) {
    clearInterval(trainTimer)
    trainTimer = null
  }
}

/** 获取训练数据 */
const fetchTrainData = async () => {
  if (!currentTrain.value) return
  
  try {
    // 并行获取训练结果和日志
    
    const resultResponse = await TrainApi.getTrainStatus(currentTrain.value.id)
    
    // 处理训练结果
    if (resultResponse && resultResponse.trainId) {
      // 设置训练数据
      trainData.value = resultResponse
      trainProgress.value = 100 // 如果有训练结果，说明已完成，进度设为100%
    } else {
      // 没有结果数据，说明未训练
      trainData.value = {
        trainId: currentTrain.value.id,
        path: null,
        rate: null,
        dataId: null
      }
      trainProgress.value = 0
    }
    const logResponse = await TrainApi.getTrainInfoStatus(currentTrain.value.id)
    // console.log(logResponse);
    
    // 处理日志数据 - 将API返回的data数组直接写入日志
    if ( logResponse && Array.isArray(logResponse)) {
      trainLogs.value = logResponse
      // 过滤掉空字符串和重复的日志
      // 过滤掉空字符串并直接替换日志
      trainLogs.value = logResponse
        .filter(logItem => logItem && logItem.trim())
        .map(logItem => ({
          timestamp: Date.now(),
          message: logItem
        }))
      
     
    }
  } catch (error) {
    console.error('获取训练数据失败:', error)
    trainData.value = {
      trainId: currentTrain.value.id,
      path: null,
      rate: null,
      dataId: null
    }
    trainProgress.value = 0
  }
}

/** 获取训练状态 */
const getTrainingStatus = () => {
  if (!trainData.value) return '未训练'
  console.log(trainData.value);
  
  
  // 如果只有 trainId, path, rate, dataId，说明未训练
  if (trainData.value.id === undefined ) {
    return '未训练'
  }else  {
    return '已完成'
  }
  
}

/** 从rate字符串中获取指标值 */
const getMetricsValue = (metric: string) => {
  if (!trainData.value?.rate || trainData.value.rate === 'null') return null
  
  const rateStr = trainData.value.rate
  const metrics: Record<string, string> = {}
  
  // 解析 "precision:0.7850,recall:0.7120,map:0.6450" 格式
  rateStr.split(',').forEach(item => {
    const [key, value] = item.split(':')
    if (key && value) {
      metrics[key.trim().toLowerCase()] = value.trim()
    }
  })
  
  // 从info中提取损失值
  if (metric === 'loss' && trainData.value.info) {
    const lossMatch = trainData.value.info.match(/损失值[:\s]+([\d.]+)/)
    if (lossMatch) {
      return lossMatch[1]
    }
  }
  
  return metrics[metric] || null
}

/** 开始训练 */
const startTraining = async () => {
  if (!currentTrain.value) return
  
  try {
    isTraining.value = true
    await TrainApi.handleTrain(currentTrain.value)
    message.success(`开始训练: ${currentTrain.value.name}`)
  } catch (error) {
    console.error('启动训练失败:', error)
    message.error('启动训练失败')
  } finally {
    isTraining.value = false
  }
}

/** 导出训练数据 */
const exportTrainData = async () => {
  if (!currentTrain.value) return
  
  try {
    const data = await TrainApi.exportTrainData(currentTrain.value.id)
    download.excel(data, `训练数据_${currentTrain.value.name}.xlsx`)
    message.success('导出成功')
  } catch (error) {
    console.error('导出失败:', error)
    message.error('导出失败')
  }
}

/** 格式化时间 */
const formatTime = (timestamp: number) => {
  return new Date(timestamp).toLocaleTimeString()
}

/** 获取状态类型 */
const getStatusType = (status: string) => {
  const statusMap: Record<string, string> = {
    'pending': 'info',
    'running': 'warning', 
    'completed': 'success',
    'failed': 'danger'
  }
  return statusMap[status] || 'info'
}

/** 获取状态文本 */
const getStatusText = (status: string) => {
  const statusMap: Record<string, string> = {
    'pending': '等待中',
    'running': '训练中',
    'completed': '已完成', 
    'failed': '失败'
  }
  return statusMap[status] || '未知'
}

/** 判断是否有训练结果 */
const hasTrainResult = computed(() => {
  return trainData.value && 
         trainData.value.round !== undefined && 
         trainData.value.roundTotal !== undefined &&
         trainData.value.round > 0
})

/** 训练按钮操作 */
const handleTrain = async (row: TrainVO) => {
  try {
    await TrainApi.handleTrain(row);
  } catch (error) {
    console.error('训练失败:', error)
  }
}
const initTrain = async (row: TrainVO) => {
  try {
    await TrainApi.InfinityTrain(row);
    message.info(`初始化完成: ${row.name}`)
    getList()
    // TODO: 实现训练逻辑
  } catch (error) {
    console.error('初始化失败:', error)
  }
}

const environmentInquiry = async () => {
  try {
    await TrainApi.environmentInquiry();
       // TODO: 实现训练逻辑
  } catch (error) {
    console.error('训练失败:', error)
  }
}
/** 测试按钮操作 */
const handleInfo = async (row: TrainVO) => {
  if (!row.dataId) return
  
  testLoading.value = true
  try {
    // 获取图片列表
    const response = await TrainApi.getTestImages(row.dataId)
    imageList.value = response || []
    currentTestTrain.value = row
    testDialogVisible.value = true
  } catch (error) {
    console.error('获取图片列表失败:', error)
    message.error('获取图片列表失败')
  } finally {
    testLoading.value = false
  }
}

/** 选择图片进行测试 */
const selectImage = async (image: string) => {
  if (!image || !currentTestTrain.value?.dataId) return
  
  selectedImage.value = image
  testLoading.value = true
  
  try {
    // 发送识别请求
    const response = await TrainApi.testRecognition({
      trainId: currentTestTrain.value.id,
      imageUrl: image
    })
    selectedTrainImage.value = response
    console.log(selectedTrainImage);
    
    message.success('识别成功')
  } catch (error) {
    console.error('识别失败:', error)
    message.error('识别失败')
    testResult.value = null
  } finally {
    testLoading.value = false
  }
}

/** 显示新增图片对话框 */
const showAddImageDialog = () => {
  uploadFiles.value = []
  addImageDialogVisible.value = true
}

/** 文件选择变化 */
const handleFileChange = (file: any, fileList: any[]) => {
  uploadFiles.value = fileList.map(item => item.raw).filter(Boolean)
}

/** 移除文件 */
const handleFileRemove = (file: any, fileList: any[]) => {
  uploadFiles.value = fileList.map(item => item.raw).filter(Boolean)
}

/** 上传前验证 */
const beforeUpload = (file: File) => {
  const isImage = file.type.startsWith('image/')
  const isLt10M = file.size / 1024 / 1024 < 10

  if (!isImage) {
    message.error('只能上传图片文件!')
    return false
  }
  if (!isLt10M) {
    message.error('图片大小不能超过 10MB!')
    return false
  }
  return false // 阻止自动上传
}

/** 上传图片 */
const uploadImages = async () => {
  if (uploadFiles.value.length === 0) {
    message.warning('请选择要上传的图片')
    return
  }
  
  try {
    addImageLoading.value = true
    
    // 调用API上传图片
    const response = await TrainApi.uploadTestImages(uploadFiles.value)
    
    // 将返回的URL添加到现有图片列表
    if (response && Array.isArray(response)) {
      imageList.value = [...imageList.value, ...response]
      message.success(`成功上传 ${response.length} 张图片`)
    } else {
      message.success('图片上传成功')
    }
    
    // 清空上传文件列表
    uploadFiles.value = []
    addImageDialogVisible.value = false
  } catch (error) {
    console.error('上传图片失败:', error)
    message.error('上传图片失败')
  } finally {
    addImageLoading.value = false
  }
}

/** 取消上传 */
const cancelUpload = () => {
  uploadFiles.value = []
  if (uploadRef.value) {
    uploadRef.value.clearFiles()
  }
  addImageDialogVisible.value = false
}

/** 清空图片列表 */
const clearImageList = async () => {
  if (!currentTestTrain.value?.dataId) return
  
  try {
    // 确认对话框
    await message.confirm('确认清空所有测试图片吗？此操作不可恢复！')
    
    // 调用API清空图片列表
    await TrainApi.clearTestImages()
    
    // 清空本地数据
    imageList.value = []
    selectedImage.value = ''
    testResult.value = null
    
    message.success('图片列表已清空')
  } catch (error) {
    if (error !== 'cancel') {
      console.error('清空图片列表失败:', error)
      message.error('清空图片列表失败')
    }
  }
}

/** 删除按钮操作 */
const handleDelete = async (id: number) => {
  try {
    // 删除的二次确认
    await message.delConfirm()
    // 发起删除
    await TrainApi.deleteTrain(id)
    message.success(t('common.delSuccess'))
    // 刷新列表
    await getList()
  } catch {}
}

/** 导出按钮操作 */
const handleExport = async () => {
  try {
    // 导出的二次确认
    await message.exportConfirm()
    // 发起导出
    exportLoading.value = true
    const data = await TrainApi.exportTrain(queryParams)
    download.excel(data, '训练.xls')
  } catch {
  } finally {
    exportLoading.value = false
  }
}

/** 初始化 **/
onMounted(() => {
  getProjectList()
  getList()
})

onUnmounted(() => {
  stopPolling()
})
</script>

<style scoped>
.train-drawer-content {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: 0 16px;
}

.train-info {
  flex-shrink: 0;
}

.train-info h3 {
  margin-bottom: 16px;
  color: #303133;
}

.train-stats {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.stat-item {
  display: flex;
  align-items: center;
  gap: 12px;
}

.stat-label {
  min-width: 80px;
  font-weight: 500;
  color: #606266;
}

.stat-value {
  font-weight: 600;
  color: #303133;
}

.loading-placeholder {
  margin-top: 16px;
}

.train-logs {
  flex: 1;
  margin-top: 24px;
  display: flex;
  flex-direction: column;
}

.train-logs h4 {
  margin-bottom: 12px;
  color: #303133;
}

.log-container {
  flex: 1;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  padding: 8px;
}

.log-content {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.log-item {
  display: flex;
  gap: 12px;
  padding: 4px 8px;
  border-radius: 4px;
  font-family: 'Courier New', monospace;
  font-size: 12px;
}

.log-item:hover {
  background-color: #f5f7fa;
}

.log-time {
  color: #909399;
  flex-shrink: 0;
}

.log-message {
  color: #606266;
  word-break: break-all;
}

.empty-logs {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100px;
}

.drawer-footer {
  display: flex;
  gap: 12px;
  padding: 16px 0;
}

/* 测试对话框样式 */
.test-dialog-content {
  display: flex;
  height: 50vh;
  gap: 20px;
}

.test-left {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.test-left h4 {
  margin-bottom: 16px;
  color: #303133;
}

.result-container {
  flex: 1;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  padding: 16px;
  overflow-y: auto;
}

.result-content {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.result-image {
  max-width: 100%;
  max-height: 100%;
  border-radius: 4px;
  object-fit: contain;
}

.result-data {
  background-color: #f5f7fa;
  border-radius: 4px;
  padding: 12px;
  max-height: 200px;
  overflow-y: auto;
}

.result-data pre {
  margin: 0;
  font-size: 12px;
  line-height: 1.4;
  color: #606266;
}

.result-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
}

.test-right {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.test-right-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}

.test-right-header h4 {
  margin: 0;
  color: #303133;
}

.test-right-buttons {
  display: flex;
  gap: 8px;
}

.image-list-container {
  flex: 1;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  overflow: hidden;
}

.image-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
  gap: 8px;
  padding: 8px;
}

.image-item {
  cursor: pointer;
  border: 2px solid transparent;
  border-radius: 4px;
  padding: 4px;
  transition: all 0.3s;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #f5f7fa;
}

.image-item:hover {
  border-color: #409eff;
  transform: scale(1.05);
}

.image-item.selected {
  border-color: #409eff;
  background-color: #ecf5ff;
}

.thumbnail {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 2px;
}

.empty-image {
  color: #909399;
  font-size: 12px;
  text-align: center;
}

/* 上传相关样式 */
.upload-container {
  padding: 20px 0;
}

.upload-dragger {
  width: 100%;
}

:deep(.el-upload-dragger) {
  width: 100%;
  height: 180px;
  border: 2px dashed #dcdfe6;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: 0.3s;
}

:deep(.el-upload-dragger:hover) {
  border-color: #409eff;
}

:deep(.el-icon--upload) {
  font-size: 67px;
  color: #c0c4cc;
  margin: 40px 0 16px;
  line-height: 50px;
}

:deep(.el-upload__text) {
  color: #606266;
  font-size: 14px;
  text-align: center;
}

:deep(.el-upload__text em) {
  color: #409eff;
  font-style: normal;
}

:deep(.el-upload__tip) {
  font-size: 12px;
  color: #606266;
  margin-top: 7px;
}
</style>