<!--
  组件名称：TaskList.vue
  组件描述：性能测试任务管理列表（CRUD + 执行/停止操作）

  @author czj
  @date 2026-08-12
-->

<template>
  <div class="task-list-page">
    <div class="toolbar">
      <el-button type="primary" @click="openCreateDialog">
        <el-icon><Plus /></el-icon>新建任务
      </el-button>
      <el-select v-model="statusFilter" placeholder="全部状态" clearable style="width: 140px" @change="loadTasks">
        <el-option label="全部" value="" />
        <el-option label="等待中" value="pending" />
        <el-option label="运行中" value="running" />
        <el-option label="已完成" value="completed" />
        <el-option label="失败" value="failed" />
        <el-option label="已取消" value="cancelled" />
      </el-select>
      <el-button @click="loadTasks"><el-icon><Refresh /></el-icon>刷新</el-button>
    </div>

    <el-card shadow="never">
      <el-table :data="tasks" v-loading="loading" stripe style="width: 100%">
        <el-table-column prop="name" label="任务名称" min-width="160" show-overflow-tooltip />
        <el-table-column prop="targetUrl" label="目标 URL" min-width="200" show-overflow-tooltip />
        <el-table-column prop="method" label="方法" width="80" />
        <el-table-column label="模式" width="90">
          <template #default="{ row }">
            <el-tag size="small">{{ modeLabel(row.mode) }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="并发" width="70" prop="concurrency" />
        <el-table-column label="时长" width="80">
          <template #default="{ row }">{{ row.duration }}s</template>
        </el-table-column>
        <el-table-column label="状态" width="110">
          <template #default="{ row }">
            <el-tag :type="statusType(row.status)" size="small">
              {{ statusLabel(row.status) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="TPS" width="90">
          <template #default="{ row }">
            {{ row.actualTps != null ? row.actualTps.toFixed(1) : '-' }}
          </template>
        </el-table-column>
        <el-table-column label="错误率" width="90">
          <template #default="{ row }">
            <span :style="{ color: (row.errorRate || 0) > 5 ? '#f56c6c' : '#67c23a' }">
              {{ row.errorRate != null ? (row.errorRate * 100).toFixed(1) + '%' : '-' }}
            </span>
          </template>
        </el-table-column>
        <el-table-column label="创建时间" width="170">
          <template #default="{ row }">{{ formatTime(row.createdAt) }}</template>
        </el-table-column>
        <el-table-column label="操作" width="250" fixed="right">
          <template #default="{ row }">
            <el-button v-if="row.status === 'pending' || row.status === 'completed' || row.status === 'failed' || row.status === 'cancelled'"
              link type="primary" size="small" :loading="runningId === row.id" @click="handleRun(row)">
              执行
            </el-button>
            <el-button v-if="row.status === 'running'" link type="warning" size="small" @click="handleStop(row)">
              停止
            </el-button>
            <el-button v-if="row.status === 'running' || row.status === 'completed'"
              link type="primary" size="small" @click="router.push('/performance/monitor?taskId=' + row.id)">
              监控
            </el-button>
            <el-button v-if="row.status === 'completed' || row.status === 'failed'"
              link type="primary" size="small" @click="router.push('/performance/report?taskId=' + row.id)">
              报告
            </el-button>
            <el-button link type="primary" size="small" @click="openEditDialog(row)">
              编辑
            </el-button>
            <el-popconfirm title="确定删除此任务？" @confirm="handleDelete(row)">
              <template #reference>
                <el-button link type="danger" size="small">删除</el-button>
              </template>
            </el-popconfirm>
          </template>
        </el-table-column>
      </el-table>

      <div class="pagination">
        <el-pagination
          v-model:current-page="page"
          v-model:page-size="pageSize"
          :total="total"
          :page-sizes="[10, 20, 50]"
          layout="total, sizes, prev, pager, next"
          @change="loadTasks"
        />
      </div>
    </el-card>

    <!-- 创建/编辑任务弹窗 -->
    <el-dialog v-model="dialogVisible" :title="editingTask ? '编辑任务' : '新建任务'" width="680px" top="3vh">
      <el-form ref="formRef" :model="form" label-position="top" :rules="formRules">
        <el-row :gutter="16">
          <el-col :span="16">
            <el-form-item label="任务名称" prop="name">
              <el-input v-model="form.name" placeholder="输入任务名称" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="接口预设">
              <el-select
                v-model="form.presetId"
                placeholder="选择预设…"
                clearable
                style="width: 100%"
                @change="onPresetChange"
              >
                <el-option label="无预设（手动填写）" value="" />
                <el-option v-for="p in presets" :key="p.id" :label="p.name" :value="p.id" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>

        <!-- 预设摘要卡片 -->
        <el-card v-if="selectedPreset" shadow="never" class="preset-summary">
          <template #header>
            <div class="preset-summary-header">
              <span class="preset-summary-title">{{ selectedPreset.name }}</span>
              <el-tag size="small" :type="methodTagType(selectedPreset.method)">{{ selectedPreset.method }}</el-tag>
            </div>
          </template>
          <div class="preset-summary-body">
            <div><strong>目标 URL：</strong><code>{{ selectedPreset.targetUrl }}</code></div>
            <div v-if="selectedPreset.description"><strong>描述：</strong>{{ selectedPreset.description }}</div>
            <div>
              <strong>认证：</strong>
              {{ selectedPreset.authRequired ? '需要登录' : '无需登录' }}<template v-if="selectedPreset.authRequired">（{{ accountKeyLabel(selectedPreset.accountKey) }}）</template>
               · 签名：{{ selectedPreset.signRequest ? '需要' : '不需要' }}
            </div>
            <el-button link type="primary" size="small" class="preset-summary-clear" @click="onPresetChange('')">
              清除预设改为手动
            </el-button>
          </div>
        </el-card>

        <!-- 从 curl 导入 -->
        <div style="margin-bottom: 12px">
          <el-button type="info" size="small" @click="openCurlDialog">
            <el-icon><Upload /></el-icon>从 curl 导入
          </el-button>
          <span style="color: #909399; font-size: 12px; margin-left: 8px;">粘贴 curl 命令自动回填接口配置</span>
        </div>

        <el-row :gutter="16">
          <el-col :span="8">
            <el-form-item label="HTTP 方法" prop="method">
              <el-select v-model="form.method" style="width: 100%" :disabled="!!form.presetId">
                <el-option label="GET" value="GET" />
                <el-option label="POST" value="POST" />
                <el-option label="PUT" value="PUT" />
                <el-option label="DELETE" value="DELETE" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="16">
            <el-form-item label="目标 URL" prop="targetUrl">
              <el-input v-model="form.targetUrl" placeholder="https://192.168.5.44/platform/api/..." :disabled="!!form.presetId" />
            </el-form-item>
          </el-col>
        </el-row>

        <el-row :gutter="16">
          <el-col :span="8">
            <el-form-item label="压测模式" prop="mode">
              <el-select v-model="form.mode" style="width: 100%" @change="onModeChange">
                <el-option label="并发模式" value="concurrent" />
                <el-option label="QPS 模式" value="qps" />
                <el-option label="阶梯模式" value="step" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="并发数" prop="concurrency">
              <el-input-number v-model="form.concurrency" :min="1" :max="1000" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="压测时长(秒)" prop="duration">
              <el-input-number v-model="form.duration" :min="5" :max="3600" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>

        <el-row :gutter="16">
          <el-col :span="8">
            <el-form-item v-if="form.mode === 'qps'" label="目标 QPS" prop="qpsTarget">
              <el-input-number v-model="form.qpsTarget" :min="1" :max="100000" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="预热时间(秒)" prop="rampUp">
              <el-input-number v-model="form.rampUp" :min="0" :max="300" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item v-if="form.mode === 'step'" label="阶梯并发数" prop="stepConcurrency">
              <el-input-number v-model="form.stepConcurrency" :min="1" :max="500" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>

        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="请求头(JSON)" prop="headers">
              <el-input v-model="form.headers" type="textarea" :rows="2" placeholder='{"Content-Type": "application/json"}' :disabled="!!form.presetId" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item v-if="!form.presetId" label="请求体(JSON)" prop="body">
              <el-input v-model="form.body" type="textarea" :rows="2" placeholder='{"key": "value"}' />
            </el-form-item>
            <el-form-item v-else label="请求体(预设模板)">
              <div class="preset-body-view">
                <pre>{{ presetBodyView }}</pre>
              </div>
            </el-form-item>
          </el-col>
        </el-row>

        <el-row :gutter="16">
          <el-col :span="8">
            <el-form-item label="需要登录认证">
              <el-switch v-model="form.authRequired" :disabled="!!form.presetId" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="请求需要签名">
              <el-switch v-model="form.signRequest" :disabled="!!form.presetId" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item v-if="form.authRequired" label="账号" prop="accountKey">
              <el-select v-model="form.accountKey" style="width: 100%" :disabled="!!form.presetId">
                <el-option label="超级管理员" value="superadmin" />
                <el-option label="管理员" value="admin" />
                <el-option label="普通用户" value="user" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>

        <el-row v-if="form.authRequired" :gutter="16">
          <el-col :span="12">
            <el-form-item label="登录用户名">
              <el-input v-model="form.loginUsername" placeholder="自定义登录用户名，留空使用默认" clearable :disabled="!!form.presetId" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="登录密码">
              <el-input v-model="form.loginPassword" type="password" show-password placeholder="自定义登录密码，留空使用默认" clearable :disabled="!!form.presetId" />
            </el-form-item>
          </el-col>
        </el-row>

        <el-form-item label="描述">
          <el-input v-model="form.description" type="textarea" :rows="2" placeholder="任务描述（可选）" />
        </el-form-item>

        <el-collapse v-if="!form.presetId">
          <el-collapse-item name="uniqueFields" title="唯一性字段配置（可选）">
            <div class="unique-field-tip">
              压测并发下部分业务字段（如模板名称 templateName）不允许重复。配置后每个请求会自动生成唯一值：
              后缀递增（001 → 001_req_000）或自定义模板（如 <code>{__UUID__}</code>）。
              字段路径支持 JSON Path，如 <code>templateName</code>、<code>$[0].templateName</code>、<code>topicList[0].topicName</code>。
            </div>
            <div v-for="(rule, index) in form.uniqueFields" :key="index" class="unique-field-row">
              <el-input
                v-model="rule.field"
                placeholder="字段路径，如 templateName / $[0].templateName"
                clearable
                style="flex: 1"
              />
              <el-select v-model="rule.strategy" style="width: 140px">
                <el-option label="后缀递增" value="suffix" />
                <el-option label="自定义模板" value="template" />
              </el-select>
              <el-input
                v-if="rule.strategy === 'template'"
                v-model="rule.template"
                placeholder="{__UUID__}"
                clearable
                style="width: 200px"
              />
              <el-button
                type="danger"
                circle
                :icon="Delete"
                @click="removeUniqueField(index)"
              />
            </div>
            <el-button type="primary" plain :icon="Plus" @click="addUniqueField">
              添加唯一性字段
            </el-button>
          </el-collapse-item>
        </el-collapse>
      </el-form>

      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" :loading="saving" @click="handleSave">保存</el-button>
      </template>
    </el-dialog>

    <!-- 从 curl 导入对话框 -->
    <el-dialog v-model="curlDialogVisible" title="从 curl 导入" width="580px" top="20vh" :close-on-click-modal="false">
      <el-input
        v-model="curlCommand"
        type="textarea"
        :rows="6"
        placeholder="粘贴 curl 命令，例如：&#10;curl -X POST https://192.168.5.44/api/xxx \&#10;  -H &quot;Content-Type: application/json&quot; \&#10;  -d '{&quot;key&quot;: &quot;value&quot;}'"
      />
      <div v-if="curlErrors.length" style="margin-top: 12px">
        <el-alert
          v-for="(err, i) in curlErrors"
          :key="i"
          :title="err"
          type="warning"
          show-icon
          :closable="false"
          style="margin-bottom: 6px"
        />
      </div>
      <template #footer>
        <el-button @click="curlDialogVisible = false">取消</el-button>
        <el-button type="primary" :loading="curlParsing" @click="handleCurlParse">解析导入</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
/**
 * 性能测试任务管理列表
 *
 * 功能：CRUD 操作、执行/停止任务、跳转到监控/报告
 */

import { ref, onMounted, reactive, computed } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Plus, Refresh, Upload, Delete } from '@element-plus/icons-vue'
import { listTasks, getTask, createTask, updateTask, deleteTask, runTask, stopTask, listPresets, getPreset, parseCurl } from '@/api/performance'
import type { PerformanceTask, PerformanceTaskListItem, PerformanceTaskCreate, ApiPresetListItem, ApiPreset, CurlParseResult, UniqueFieldRule } from '@/types/performance'

const router = useRouter()

const loading = ref(false)
const tasks = ref<PerformanceTaskListItem[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const statusFilter = ref('')

const dialogVisible = ref(false)
const editingTask = ref<PerformanceTask | null>(null)
const saving = ref(false)
const runningId = ref('')
const formRef = ref()

// 预设相关
const presets = ref<ApiPresetListItem[]>([])
const selectedPreset = ref<ApiPreset | null>(null)

// curl 导入相关
const curlDialogVisible = ref(false)
const curlCommand = ref('')
const curlParsing = ref(false)
const curlErrors = ref<string[]>([])

interface TaskForm {
  name: string
  description: string | null
  targetUrl: string
  method: string
  headers: string
  body: string
  bodyTemplate: string | null
  mode: 'concurrent' | 'qps' | 'step'
  concurrency: number
  qpsTarget: number | null
  duration: number
  rampUp: number | null
  stepConcurrency: number | null
  stepDuration: number | null
  authRequired: boolean
  accountKey: string | null
  loginUsername: string
  loginPassword: string
  signRequest: boolean
  assertions: any[]
  presetId: string
  uniqueFields: UniqueFieldRule[]
}

const defaultForm = (): TaskForm => ({
  name: '',
  description: null,
  targetUrl: '',
  method: 'GET',
  headers: '',
  body: '',
  bodyTemplate: null,
  mode: 'concurrent',
  concurrency: 10,
  qpsTarget: null,
  duration: 60,
  rampUp: null,
  stepConcurrency: null,
  stepDuration: null,
  authRequired: true,
  accountKey: 'admin',
  loginUsername: '',
  loginPassword: '',
  signRequest: false,
  assertions: [],
  presetId: '',
  uniqueFields: [],
})

const form = reactive<TaskForm>(defaultForm())

/** 预设请求体只读展示 */
const presetBodyView = computed(() => {
  const p = selectedPreset.value
  if (!p) return ''
  if (p.bodyTemplate) {
    return JSON.stringify(p.bodyTemplate, null, 2)
  }
  return p.body != null ? JSON.stringify(p.body, null, 2) : ''
})

const formRules = {
  name: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
  targetUrl: [{ required: true, message: '请输入目标 URL', trigger: 'blur' }],
  method: [{ required: true, message: '请选择 HTTP 方法', trigger: 'change' }],
  mode: [{ required: true, message: '请选择压测模式', trigger: 'change' }],
}

/** 加载任务列表 */
async function loadTasks() {
  loading.value = true
  try {
    const params: any = { page: page.value, pageSize: pageSize.value }
    if (statusFilter.value) params.status = statusFilter.value
    const res = await listTasks(params)
    tasks.value = res.items
    total.value = res.total
  } catch (e: any) {
    ElMessage.error('加载任务列表失败: ' + (e.message || ''))
  } finally {
    loading.value = false
  }
}

/** 打开新建弹窗 */
async function openCreateDialog() {
  editingTask.value = null
  selectedPreset.value = null
  Object.assign(form, defaultForm())
  await loadPresets()
  dialogVisible.value = true
}

/** 打开编辑弹窗 */
async function openEditDialog(task: PerformanceTaskListItem) {
  // 表格行是 PerformanceTaskListItem（缺少 body/headers/stepConcurrency 等字段），
  // 必须调用 getTask 获取完整详情再填充表单
  let fullTask: PerformanceTask
  try {
    fullTask = await getTask(task.id)
  } catch {
    // 回退到行数据（至少能显示名称/URL/模式等基本信息）
    fullTask = task as unknown as PerformanceTask
  }
  editingTask.value = fullTask

  // 加载预设列表
  await loadPresets()

  // 如果任务有 presetId，找到对应的预设并回填
  const presetId = fullTask.presetId || ''
  if (presetId) {
    const found = presets.value.find(p => p.id === presetId)
    if (found) {
      // 获取完整预设数据
      try {
        const preset = await getPreset(presetId)
        selectedPreset.value = preset
      } catch {
        selectedPreset.value = null
      }
    } else {
      // 预设已被删除：清除关联，字段保持可编辑
      selectedPreset.value = null
    }
  } else {
    selectedPreset.value = null
  }

  Object.assign(form, {
    name: fullTask.name,
    description: fullTask.description,
    targetUrl: fullTask.targetUrl,
    method: fullTask.method,
    headers: fullTask.headers ? JSON.stringify(fullTask.headers, null, 2) : '',
    body: fullTask.body ? (typeof fullTask.body === 'string' ? fullTask.body : JSON.stringify(fullTask.body, null, 2)) : '',
    bodyTemplate: fullTask.bodyTemplate,
    mode: fullTask.mode,
    concurrency: fullTask.concurrency,
    qpsTarget: fullTask.qpsTarget,
    duration: fullTask.duration,
    rampUp: fullTask.rampUp,
    stepConcurrency: Array.isArray(fullTask.stepConcurrency) ? Math.max(...fullTask.stepConcurrency) : fullTask.stepConcurrency,
    stepDuration: fullTask.stepDuration,
    authRequired: fullTask.authRequired,
    accountKey: fullTask.accountKey,
    loginUsername: fullTask.loginUsername || '',
    loginPassword: fullTask.loginPassword || '',
    signRequest: fullTask.signRequest,
    assertions: fullTask.assertions || [],
    presetId: presetId && selectedPreset.value ? presetId : '',
    uniqueFields: (fullTask.uniqueFields || []).map(r => ({ ...r })),
  })
  dialogVisible.value = true
}

/** 模式切换 */
function onModeChange(mode: string) {
  if (mode === 'qps' && !form.qpsTarget) form.qpsTarget = 100
  if (mode === 'step') {
    if (!form.stepConcurrency) form.stepConcurrency = 20
    if (!form.stepDuration) form.stepDuration = 30
  }
}

/** 加载预设列表 */
async function loadPresets() {
  try {
    const res = await listPresets({ pageSize: 200 })
    presets.value = res.items || []
  } catch {
    presets.value = []
  }
}

/** 预设切换：填充接口字段或清空恢复到手动 */
async function onPresetChange(presetId: string) {
  if (!presetId) {
    // 清空预设，恢复到手动填写
    selectedPreset.value = null
    form.method = 'GET'
    form.targetUrl = ''
    form.headers = ''
    form.body = ''
    form.bodyTemplate = null
    form.authRequired = true
    form.signRequest = false
    form.accountKey = 'admin'
    form.presetId = ''
    return
  }

  // 获取完整预设数据
  try {
    const preset = await getPreset(presetId) as ApiPreset
    selectedPreset.value = preset

    // 填充接口字段
    form.method = preset.method
    form.targetUrl = preset.targetUrl
    form.headers = preset.headers ? JSON.stringify(preset.headers, null, 2) : ''
    if (preset.bodyTemplate) {
      form.body = ''
      form.bodyTemplate = JSON.stringify(preset.bodyTemplate, null, 2)
    } else {
      form.bodyTemplate = null
      form.body = preset.body != null ? JSON.stringify(preset.body, null, 2) : ''
    }
    form.authRequired = preset.authRequired
    form.signRequest = preset.signRequest
    form.accountKey = preset.accountKey || 'superadmin'
    form.presetId = preset.id
  } catch (e: any) {
    ElMessage.error('获取预设详情失败: ' + (e.message || ''))
    selectedPreset.value = null
    form.presetId = ''
  }
}

/** 打开 curl 导入对话框 */
function openCurlDialog() {
  curlCommand.value = ''
  curlErrors.value = []
  curlDialogVisible.value = true
}

/** 调用后端解析 curl，回填表单 */
async function handleCurlParse() {
  const cmd = curlCommand.value.trim()
  if (!cmd) {
    ElMessage.warning('请先粘贴 curl 命令')
    return
  }

  curlParsing.value = true
  try {
    const res = parseCurl(cmd)
    // 后端 400 时走 catch，成功直接回填
    const result: CurlParseResult = await res
    applyCurlResult(result)
    curlDialogVisible.value = false
  } catch (e: any) {
    ElMessage.error('curl 解析失败: ' + extractApiError(e))
  } finally {
    curlParsing.value = false
  }
}

/** 将解析结果回填到任务表单 */
function applyCurlResult(result: CurlParseResult) {
  // 解除预设关联（回填值与预设无关，恢复接口字段可编辑）
  selectedPreset.value = null
  form.presetId = ''

  form.method = result.method || 'GET'
  form.targetUrl = result.targetUrl || ''

  // headers → 格式化 JSON 字符串
  form.headers = Object.keys(result.headers || {}).length
    ? JSON.stringify(result.headers, null, 2)
    : ''

  // body → 对象/数组格式化为 JSON，字符串原样，null 清空
  if (result.body != null) {
    form.body = typeof result.body === 'string' ? result.body : JSON.stringify(result.body, null, 2)
  } else {
    form.body = ''
  }

  form.authRequired = !!result.authRequired
  form.signRequest = !!result.signRequest
  if (!form.authRequired) form.accountKey = 'admin'

  // 不支持参数提示（不阻断回填）
  curlErrors.value = result.errors || []
  if (curlErrors.value.length) {
    // 延迟到对话框关闭后展示，避免被覆盖
    setTimeout(() => {
      curlErrors.value.forEach((err: string) => ElMessage.warning(err))
    }, 100)
  }
}

/** 账号标签 */
function accountKeyLabel(key: string) {
  const map: Record<string, string> = { superadmin: '超级管理员', admin: '管理员', user: '普通用户' }
  return map[key] || key
}

/** 添加唯一性字段规则 */
function addUniqueField() {
  form.uniqueFields.push({ field: '', strategy: 'suffix', template: null })
}

/** 删除唯一性字段规则 */
function removeUniqueField(index: number) {
  form.uniqueFields.splice(index, 1)
}

/** 方法标签颜色 */
function methodTagType(method: string) {
  const map: Record<string, string> = { GET: 'success', POST: 'primary', PUT: 'warning', DELETE: 'danger' }
  return map[method] || 'info'
}

/** 提取后端校验错误的可读信息（Pydantic detail 可能是数组） */
function extractApiError(e: any): string {
  const detail = e?.response?.data?.detail
  if (Array.isArray(detail)) {
    return detail
      .map((d: any) => {
        const loc = Array.isArray(d?.loc) ? d.loc.filter((x: any) => x !== 'body').join('.') : ''
        return loc ? `${loc}: ${d?.msg || ''}` : (d?.msg || '')
      })
      .join('；')
  }
  return detail || e?.message || ''
}

/** 保存任务 */
async function handleSave() {
  if (!formRef.value) return
  try {
    await formRef.value.validate()
  } catch {
    return
  }

  saving.value = true
  try {
    const data: any = {
      name: form.name,
      description: form.description || null,
      targetUrl: form.targetUrl,
      method: form.method,
      headers: form.headers ? JSON.parse(form.headers) : null,
      body: form.body || null,
      bodyTemplate: form.bodyTemplate
        ? (typeof form.bodyTemplate === 'string' ? JSON.parse(form.bodyTemplate) : form.bodyTemplate)
        : null,
      mode: form.mode,
      concurrency: form.concurrency,
      qpsTarget: form.mode === 'qps' ? form.qpsTarget : null,
      duration: form.duration,
      rampUp: form.rampUp || null,
      stepConcurrency:
        form.mode === 'step' && form.stepConcurrency
          ? Array.isArray(form.stepConcurrency)
            ? form.stepConcurrency
            : [form.stepConcurrency]
          : null,
      stepDuration: form.mode === 'step' ? (form.stepDuration || 30) : null,
      authRequired: form.authRequired,
      accountKey: form.authRequired ? form.accountKey : null,
      loginUsername: form.loginUsername || null,
      loginPassword: form.loginPassword || null,
      signRequest: form.signRequest,
      assertions: [],
      presetId: form.presetId || null,
      uniqueFields: form.uniqueFields.length > 0 ? form.uniqueFields : null,
    }

    if (editingTask.value) {
      await updateTask(editingTask.value.id, data)
      ElMessage.success('任务已更新')
    } else {
      await createTask(data as PerformanceTaskCreate)
      ElMessage.success('任务已创建')
    }

    dialogVisible.value = false
    await loadTasks()
  } catch (e: any) {
    ElMessage.error('保存失败: ' + extractApiError(e))
  } finally {
    saving.value = false
  }
}

/** 执行任务 */
async function handleRun(task: PerformanceTaskListItem) {
  runningId.value = task.id
  try {
    const res = await runTask(task.id)
    ElMessage.success(res.message || '任务已开始执行')
    await loadTasks()
  } catch (e: any) {
    ElMessage.error('执行失败: ' + (e.response?.data?.detail || e.message || ''))
  } finally {
    runningId.value = ''
  }
}

/** 停止任务 */
async function handleStop(task: PerformanceTaskListItem) {
  try {
    const res = await stopTask(task.id)
    ElMessage.success(res.message || '已发送停止信号')
    await loadTasks()
  } catch (e: any) {
    ElMessage.error('停止失败: ' + (e.response?.data?.detail || e.message || ''))
  }
}

/** 删除任务 */
async function handleDelete(task: PerformanceTaskListItem) {
  try {
    await deleteTask(task.id)
    ElMessage.success('任务已删除')
    await loadTasks()
  } catch (e: any) {
    ElMessage.error('删除失败: ' + (e.response?.data?.detail || e.message || ''))
  }
}

/** 状态标签类型 */
function statusType(status: string) {
  const map: Record<string, string> = { completed: 'success', running: 'primary', pending: 'info', failed: 'danger', cancelled: 'warning' }
  return map[status] || 'info'
}

/** 状态标签文字 */
function statusLabel(status: string) {
  const map: Record<string, string> = { completed: '已完成', running: '运行中', pending: '等待中', failed: '失败', cancelled: '已取消' }
  return map[status] || status
}

/** 模式标签 */
function modeLabel(mode: string) {
  const map: Record<string, string> = { concurrent: '并发', qps: 'QPS', step: '阶梯' }
  return map[mode] || mode
}

/** 格式化时间 */
function formatTime(t: string) {
  return t?.replace('T', ' ').substring(0, 19) || ''
}

onMounted(() => {
  loadTasks()
})
</script>

<style scoped>
.task-list-page {
  padding: 0;
}

.toolbar {
  display: flex;
  gap: 12px;
  margin-bottom: 16px;
  align-items: center;
}

.pagination {
  display: flex;
  justify-content: flex-end;
  margin-top: 16px;
}

/* 预设摘要卡片 */
.preset-summary {
  margin-bottom: 16px;
  background: #fafafa;
}

.preset-summary-header {
  display: flex;
  align-items: center;
  gap: 8px;
}

.preset-summary-title {
  font-weight: 600;
  font-size: 14px;
}

.preset-summary-body {
  font-size: 13px;
  line-height: 1.8;
  position: relative;
  padding-right: 120px;
}

.preset-summary-body code {
  word-break: break-all;
  font-size: 12px;
}

.preset-summary-clear {
  position: absolute;
  right: 0;
  top: 0;
}

.preset-body-view {
  width: 100%;
  max-height: 90px;
  overflow: auto;
  background: #f5f7fa;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  padding: 6px 10px;
  font-size: 12px;
}

.preset-body-view pre {
  margin: 0;
  color: #606266;
}

/* 唯一性字段配置 */
.unique-field-tip {
  font-size: 12px;
  color: #909399;
  line-height: 1.7;
  margin-bottom: 10px;
}

.unique-field-tip code {
  background: #f5f7fa;
  padding: 1px 5px;
  border-radius: 3px;
  color: #409eff;
}

.unique-field-row {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-bottom: 8px;
}
</style>