<!--
  组件名称：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: 160px" @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-select v-model="projectFilter" placeholder="全部项目" clearable style="width: 180px" @change="loadTasks">
        <el-option label="全部项目" value="" />
        <el-option v-for="p in projects" :key="p.id" :label="p.name" :value="p.id" />
      </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 label="所属项目" width="130" show-overflow-tooltip>
          <template #default="{ row }">
            <el-tag v-if="row.projectId" size="small">{{ projectName(row.projectId) }}</el-tag>
            <span v-else style="color: #909399; font-size: 12px;">未归属</span>
          </template>
        </el-table-column>
        <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="100">
          <template #default="{ row }">
            <el-tag v-if="row.scenarioType === 'burst'" type="warning" size="small">瞬时并发</el-tag>
            <el-tag v-else-if="row.scenarioType === 'endurance'" type="danger" size="small">长稳压测</el-tag>
            <span v-else style="color: #909399; font-size: 12px;">标准</span>
          </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="Error Rate（错误率）" 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 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 prop="name">
              <template #label>
                任务名称
                <FieldTip content="任务名称，用于标识和搜索该任务" />
              </template>
              <el-input v-model="form.name" placeholder="输入任务名称" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item>
              <template #label>
                归属项目
                <FieldTip content="将任务归属到项目，便于按项目批量执行与合并报告" />
              </template>
              <el-select v-model="form.projectId" placeholder="选择项目（可留空）" clearable style="width: 100%">
                <el-option v-for="p in projects" :key="p.id" :label="p.name" :value="p.id" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <!-- 从 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 prop="method">
              <template #label>
                HTTP 方法
                <FieldTip content="请求的 HTTP 方法：GET（查询）、POST（新增）、PUT（更新）、DELETE（删除）" />
              </template>
              <el-select v-model="form.method" style="width: 100%">
                <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 prop="targetUrl">
              <template #label>
                目标 URL
                <FieldTip content="被测接口的完整地址。保留 {{param}} 占位符可传参，如 https://192.168.5.44/api/user/{{id}}" />
              </template>
              <el-input v-model="form.targetUrl" placeholder="https://192.168.5.44/platform/api/..." />
            </el-form-item>
          </el-col>
        </el-row>

        <el-row :gutter="16">
          <el-col :span="8">
            <el-form-item prop="mode">
              <template #label>
                压测模式
                <FieldTip content="并发模式：固定并发数持续压测；QPS 模式：控制每秒请求数；阶梯模式：逐步增加并发" />
              </template>
              <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>
              <template #label>
                场景类型
                <FieldTip content="标准模式：常规压测；瞬时并发 Burst：所有用户同时发请求（集合点）；长稳压测 Endurance：持续较长时间运行" />
              </template>
              <el-select v-model="form.scenarioType" placeholder="标准模式" clearable style="width: 100%" @change="onScenarioTypeChange">
                <el-option label="标准模式" value="" />
                <el-option label="瞬时并发（Burst）" value="burst" />
                <el-option label="长稳压测（Endurance）" value="endurance" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item prop="concurrency">
              <template #label>
                并发数
                <FieldTip content="同时发送请求的虚拟用户数。模拟多用户并发访问，总并发数不应超过服务器承载能力" />
              </template>
              <el-input-number v-model="form.concurrency" :min="1" :max="1000" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>

        <el-row :gutter="16">
          <el-col :span="8">
            <el-form-item prop="duration">
              <template #label>
                压测时长（秒）
                <FieldTip content="压测持续的总时间（秒）。建议按场景设定：快速验证 30-60s，正式测试 120-300s" />
              </template>
              <el-input-number v-model="form.duration" :min="5" :max="3600" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item v-if="form.mode === 'qps'" prop="qpsTarget">
              <template #label>
                目标 QPS
                <FieldTip content="每秒期望发送的请求数。系统会动态调整并发数以维持目标 QPS，适合控制吞吐量场景" />
              </template>
              <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 prop="rampUp">
              <template #label>
                预热时间（秒）
                <FieldTip content="从 0 逐步增加到目标并发数的时间。预热可避免瞬间打满造成误报，0 表示立即满负载" />
              </template>
              <el-input-number v-model="form.rampUp" :min="0" :max="300" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>

        <el-row :gutter="16" v-if="form.mode === 'step'">
          <el-col :span="8">
            <el-form-item prop="stepConcurrency">
              <template #label>
                阶梯并发数
                <FieldTip content="阶梯模式的最终并发数。从预热结束后的并发逐步增加到该值，用于观察系统在不同并发下的表现" />
              </template>
              <el-input-number v-model="form.stepConcurrency" :min="1" :max="500" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>

        <!-- ==================== 瞬时并发配置 ==================== -->
        <template v-if="form.scenarioType === 'burst'">
          <el-divider content-position="left">瞬时并发配置</el-divider>
          <el-row :gutter="16">
            <el-col :span="8">
              <el-form-item>
                <template #label>
                  循环类型
                  <FieldTip content="有限循环：按指定次数循环；无限循环：在压测时长内持续循环，适合长时间稳定性测试" />
                </template>
                <el-select v-model="form.loopType" style="width: 100%">
                  <el-option label="有限循环" value="finite" />
                  <el-option label="无限循环" value="infinite" />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item v-if="form.loopType === 'finite'">
                <template #label>
                  循环次数
                  <FieldTip content="每个虚拟用户执行请求的循环次数。如设 10，则每个用户连续发 10 次请求" />
                </template>
                <el-input-number v-model="form.loopCount" :min="1" :max="100000" style="width: 100%" />
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item>
                <template #label>
                  请求失败时
                  <FieldTip content="continue：请求失败后继续执行，统计错误率；stop：请求失败时立即终止整个压测" />
                </template>
                <el-select v-model="form.errorAction" style="width: 100%">
                  <el-option label="继续执行" value="continue" />
                  <el-option label="立即停止" value="stop" />
                </el-select>
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="16">
            <el-col :span="8">
              <el-form-item>
                <template #label>
                  集合点同步
                  <FieldTip content="启用后所有虚拟用户在同一时刻发送请求，模拟瞬时高并发场景。关闭则每个用户独立发送" />
                </template>
                <el-switch v-model="form.synchronizingTimerEnabled" />
              </el-form-item>
            </el-col>
            <el-col :span="8" v-if="form.synchronizingTimerEnabled">
              <el-form-item>
                <template #label>
                  集合点线程数
                  <FieldTip content="达到该数量的虚拟用户后同时放行。建议等于总并发数，使所有用户同时发起请求" />
                </template>
                <el-input-number v-model="form.synchronizingTimerCount" :min="1" :max="1000" style="width: 100%" />
              </el-form-item>
            </el-col>
            <el-col :span="8" v-if="form.synchronizingTimerEnabled">
              <el-form-item>
                <template #label>
                  集合点超时（ms）
                  <FieldTip content="等待其他虚拟用户到达集合点的最长时间。超时后已到达的用户先执行，防止永久阻塞" />
                </template>
                <el-input-number v-model="form.synchronizingTimerTimeout" :min="1000" :max="120000" :step="1000" style="width: 100%" />
              </el-form-item>
            </el-col>
          </el-row>
        </template>

        <!-- ==================== 长稳压测配置 ==================== -->
        <template v-if="form.scenarioType === 'endurance'">
          <el-divider content-position="left">长稳压测配置</el-divider>

          <!-- 多接口编辑器 -->
          <div class="txn-tip">
            配置多个接口，每个接口分配独立的虚拟用户数。总并发 = 各接口线程数之和。
          </div>
          <div v-for="(api, aIdx) in form.scenarioApis" :key="aIdx" class="txn-step">
            <div class="txn-step-header">
              <el-input v-model="api.name" placeholder="接口名称，如 登录" style="width: 200px" />
              <el-select v-model="api.method" style="width: 100px">
                <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>
              <span class="txn-step-actions">
                <el-button :icon="ArrowUp" circle size="small" :disabled="aIdx === 0" @click="moveScenarioApi(aIdx, -1)" />
                <el-button :icon="ArrowDown" circle size="small" :disabled="aIdx === form.scenarioApis.length - 1" @click="moveScenarioApi(aIdx, 1)" />
                <el-button :icon="Delete" circle size="small" type="danger" @click="form.scenarioApis.splice(aIdx, 1)" />
              </span>
            </div>
            <el-input v-model="api.url" placeholder="目标 URL" />
            <el-row :gutter="12">
              <el-col :span="8">
                <el-form-item>
                  <template #label>
                    虚拟用户数
                    <FieldTip content="该接口分配的独立虚拟用户数。总并发 = 各接口线程数之和，模拟不同负载比例" />
                  </template>
                  <el-input-number v-model="api.threadCount" :min="1" :max="1000" style="width: 100%" />
                </el-form-item>
              </el-col>
              <el-col :span="16">
                <el-input v-model="api.headers" type="textarea" :rows="1" placeholder='请求头（JSON），可选，如 {"Content-Type": "application/json"}' />
              </el-col>
            </el-row>
            <el-input v-model="api.body" type="textarea" :rows="2" placeholder="请求体（JSON），可选" />
          </div>
          <el-button type="primary" plain :icon="Plus" @click="addScenarioApi">添加接口</el-button>

          <!-- 思考时间 -->
          <el-divider content-position="left">思考时间</el-divider>
          <el-row :gutter="16">
            <el-col :span="6">
              <el-form-item>
                <template #label>
                  启用思考时间
                  <FieldTip content="启用后每个请求间插入等待时间，模拟真实用户操作间隔，更接近实际使用场景" />
                </template>
                <el-switch v-model="form.thinkTimeEnabled" />
              </el-form-item>
            </el-col>
            <el-col :span="6" v-if="form.thinkTimeEnabled">
              <el-form-item>
                <template #label>
                  最小间隔（ms）
                  <FieldTip content="思考时间的最小值（毫秒）。实际等待时间在最小值和最大值之间随机" />
                </template>
                <el-input-number v-model="form.thinkTimeMin" :min="0" :max="60000" style="width: 100%" />
              </el-form-item>
            </el-col>
            <el-col :span="6" v-if="form.thinkTimeEnabled">
              <el-form-item>
                <template #label>
                  最大间隔（ms）
                  <FieldTip content="思考时间的最大值（毫秒）。实际等待时间在最小值和最大值之间随机" />
                </template>
                <el-input-number v-model="form.thinkTimeMax" :min="0" :max="60000" style="width: 100%" />
              </el-form-item>
            </el-col>
            <el-col :span="6" v-if="form.thinkTimeEnabled">
              <el-form-item>
                <template #label>
                  分布策略
                  <FieldTip content="恒定：使用中点值（(Min+Max)/2）作为固定等待时间；高斯分布：按正态分布随机取值，更加自然" />
                </template>
                <el-select v-model="form.thinkTimeDistribution" style="width: 100%">
                  <el-option label="恒定（中点值）" value="constant" />
                  <el-option label="高斯分布" value="gaussian" />
                </el-select>
              </el-form-item>
            </el-col>
          </el-row>

        </template>

        <!-- ==================== CSV 参数化（全场景通用） ==================== -->
        <template v-if="form.scenarioType === 'burst' || form.scenarioType === 'endurance' || !form.scenarioType">
          <el-divider content-position="left">CSV 参数化</el-divider>
          <el-row :gutter="16">
            <el-col :span="6">
              <el-form-item>
                <template #label>
                  启用 CSV 参数化
                  <FieldTip content="启用后从 CSV 数据文件中按行读取数据，按虚拟用户分配。登录接口可用 username/password 列实现多账号并发登录" />
                </template>
                <el-switch v-model="form.csvParameterizationEnabled" />
              </el-form-item>
            </el-col>
          </el-row>
          <template v-if="form.csvParameterizationEnabled">
            <el-form-item>
              <template #label>
                CSV 数据内容
                <FieldTip content="第一行为字段名，后续每行为一条数据。登录多账号场景列名用 username/password，每个虚拟用户取一行" />
              </template>
              <div style="display: flex; gap: 8px; align-items: flex-start; width: 100%;">
                <el-input v-model="form.csvContent" type="textarea" :rows="4" placeholder="第一行为字段名，后续行为数据行&#10;username,password&#10;user1,pass1&#10;user2,pass2" style="flex: 1;" />
                <div style="display: flex; flex-direction: column; gap: 8px; white-space: nowrap;">
                  <el-upload
                    :show-file-list="false"
                    :before-upload="handleXlsxUpload"
                    accept=".xlsx,.xls"
                  >
                    <el-button :icon="Upload" size="small">导入 XLSX</el-button>
                  </el-upload>
                  <el-button :icon="Download" size="small" @click="downloadCsvTemplate">下载模板</el-button>
                </div>
              </div>
            </el-form-item>
            <el-form-item>
              <template #label>
                字段映射（JSON）
                <FieldTip content="将 CSV 列映射到请求变量。key 为请求字段，value 用 {{csv.列名}} 引用，如 {&quot;username&quot;: &quot;{{csv.username}}&quot;}。登录接口将 username/password 映射到对应列即可实现多账号" />
              </template>
              <el-input v-model="form.csvVariableMapping" type="textarea" :rows="2" placeholder='{"username": "{{csv.username}}", "password": "{{csv.password}}"}' />
            </el-form-item>
          </template>
        </template>

        <el-form-item v-if="!form.scenarioType">
          <template #label>
            任务类型
            <FieldTip content="单接口：只压测一个接口；多步骤事务：按顺序执行多个步骤，前序响应可被后续步骤引用（更接近真实业务链路）" />
          </template>
          <el-radio-group v-model="form.taskType">
            <el-radio value="single">单接口</el-radio>
            <el-radio value="transaction">多步骤事务</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item v-else>
          <template #label>
            任务类型
            <FieldTip content="场景模式下仅支持单接口" />
          </template>
          <el-tag size="small">{{ scenarioLabel(form.scenarioType) }}</el-tag>
          <span style="color: #909399; font-size: 12px; margin-left: 8px;">场景模式下仅支持单接口</span>
        </el-form-item>

        <!-- 多步骤事务：步骤列表编辑器 -->
        <template v-if="form.taskType === 'transaction'">
          <el-divider content-position="left">事务步骤</el-divider>
          <div class="txn-tip">
            按顺序执行多个步骤，前序步骤捕获的响应字段可用 <code>{{ tplVar }}</code> 在后续步骤的 URL / Body 中引用。
            每个虚拟用户（并发）独立维护共享上下文，步骤间不互相干扰。
          </div>
          <div v-for="(step, sIdx) in form.steps" :key="sIdx" class="txn-step">
            <div class="txn-step-header">
              <el-input v-model="step.name" placeholder="步骤名称，如 登录" style="width: 220px" />
              <el-select v-model="step.method" style="width: 110px">
                <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>
              <span class="txn-step-actions">
                <el-button :icon="ArrowUp" circle size="small" :disabled="sIdx === 0" @click="moveStep(sIdx, -1)" />
                <el-button :icon="ArrowDown" circle size="small" :disabled="sIdx === form.steps.length - 1" @click="moveStep(sIdx, 1)" />
                <el-button :icon="Delete" circle size="small" type="danger" @click="form.steps.splice(sIdx, 1)" />
              </span>
            </div>
            <el-input v-model="step.url" placeholder="目标 URL，如 https://192.168.5.44/platform/api/xxx" />
            <el-row :gutter="12">
              <el-col :span="24">
                <el-input
                  v-model="step.headersText"
                  type="textarea"
                  :rows="2"
                  placeholder='请求头（JSON），可选，如 {"Content-Type": "application/json"}'
                />
              </el-col>
              <el-col :span="24">
                <el-input
                  v-model="step.bodyText"
                  type="textarea"
                  :rows="3"
                  placeholder='请求体（JSON），可选，支持 {{captured.key}} 变量，如 {"token": "{{captured.token}}"}'
                />
              </el-col>
            </el-row>
            <el-row :gutter="12">
              <el-col :span="12">
                <el-form-item label="响应捕获（供后续步骤引用）">
                  <div v-for="(cr, crIdx) in step.captureRules" :key="crIdx" class="capture-row">
                    <el-input v-model="cr.key" placeholder="变量名，如 token" style="flex: 1" />
                    <el-input v-model="cr.jsonPath" placeholder="JSON Path，如 $.data.token" style="flex: 2" />
                    <el-button :icon="Delete" circle size="small" type="danger" @click="step.captureRules.splice(crIdx, 1)" />
                  </div>
                  <el-button type="primary" plain size="small" :icon="Plus" @click="step.captureRules.push({ key: '', jsonPath: '' })">
                    添加捕获规则
                  </el-button>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="可用捕获变量">
                  <div class="capture-hint">
                    <template v-if="capturedKeys(sIdx).length">
                      <el-tag
                        v-for="k in capturedKeys(sIdx)"
                        :key="k"
                        size="small"
                        style="margin-right: 4px; cursor: pointer;"
                        @click="insertCapturedVar(sIdx, k)"
                      >{{ capturedToken(k) }}</el-tag>
                    </template>
                    <span v-else style="color: #909399; font-size: 12px;">暂无前置步骤捕获的变量</span>
                  </div>
                </el-form-item>
              </el-col>
            </el-row>
          </div>
          <el-button type="primary" plain :icon="Plus" @click="addStep">添加步骤</el-button>
        </template>

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

        <el-collapse v-if="form.taskType === 'single'">
          <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 } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Plus, Refresh, Upload, Download, Delete, ArrowUp, ArrowDown } from '@element-plus/icons-vue'
import { listTasks, getTask, createTask, updateTask, deleteTask, runTask, stopTask, listProjects, parseCurl } from '@/api/performance'
import type { PerformanceTask, PerformanceTaskListItem, PerformanceTaskCreate, CurlParseResult, UniqueFieldRule, PerformanceProject, PerfScenarioType, LoopType, ErrorAction, ThinkTimeDistribution } from '@/types/performance'
import FieldTip from '@/components/FieldTip.vue'
import * as XLSX from 'xlsx'

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 projectFilter = ref('')

// 项目相关（用于筛选列和归属选择器）
const projects = ref<PerformanceProject[]>([])

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

// 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
  mode: 'concurrent' | 'qps' | 'step'
  concurrency: number
  qpsTarget: number | null
  duration: number
  rampUp: number | null
  stepConcurrency: number | null
  stepDuration: number | null
  assertions: any[]
  uniqueFields: UniqueFieldRule[]
  projectId: string
  /** 任务类型：单接口 / 多步骤事务 */
  taskType: 'single' | 'transaction'
  /** 事务步骤配置 */
  steps: StepFormItem[]
  /** 事务失败策略: fail_fast(失败即终止) / continue(继续执行) */
  transactionFailPolicy: string
  /** 单个事务超时（秒） */
  transactionTimeout: number
  /** 场景类型：null=标准 / 'burst'=瞬时并发 / 'endurance'=长稳压测 */
  scenarioType: PerfScenarioType
  /** endurance 多接口配置 */
  scenarioApis: ScenarioApiFormItem[]
  /** 循环类型（burst） */
  loopType: LoopType
  /** 循环次数（finite 时生效） */
  loopCount: number
  /** 集合点（burst） */
  synchronizingTimerEnabled: boolean
  synchronizingTimerCount: number
  synchronizingTimerTimeout: number
  /** 错误动作（burst） */
  errorAction: ErrorAction
  /** 思考时间（endurance） */
  thinkTimeEnabled: boolean
  thinkTimeMin: number
  thinkTimeMax: number
  thinkTimeDistribution: ThinkTimeDistribution
  /** CSV 参数化 */
  csvParameterizationEnabled: boolean
  csvContent: string
  csvVariableMapping: string
}

/** endurance 场景接口表单项 */
interface ScenarioApiFormItem {
  name: string
  method: string
  url: string
  headers: string
  body: string
  threadCount: number
  assertions: any[]
}

/** 事务步骤表单项 */
interface StepFormItem {
  name: string
  url: string
  method: string
  headersText: string
  bodyText: string
  timeout: number
  captureRules: { key: string; jsonPath: string }[]
}

const defaultStep = (): StepFormItem => ({
  name: '',
  url: '',
  method: 'GET',
  headersText: '',
  bodyText: '',
  timeout: 30,
  captureRules: [],
})

const defaultForm = (): TaskForm => ({
  name: '',
  description: null,
  targetUrl: '',
  method: 'GET',
  headers: '',
  body: '',
  mode: 'concurrent',
  concurrency: 10,
  qpsTarget: null,
  duration: 60,
  rampUp: null,
  stepConcurrency: null,
  stepDuration: null,
  assertions: [],
  uniqueFields: [],
  projectId: '',
  taskType: 'single',
  steps: [],
  transactionFailPolicy: 'fail_fast',
  transactionTimeout: 60,
  // 场景增强
  scenarioType: null,
  scenarioApis: [],
  loopType: 'finite',
  loopCount: 1,
  synchronizingTimerEnabled: false,
  synchronizingTimerCount: 10,
  synchronizingTimerTimeout: 30000,
  errorAction: 'continue',
  thinkTimeEnabled: false,
  thinkTimeMin: 1000,
  thinkTimeMax: 3000,
  thinkTimeDistribution: 'constant',
  csvParameterizationEnabled: false,
  csvContent: '',
  csvVariableMapping: '',
})

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

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
    if (projectFilter.value) params.projectId = projectFilter.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 loadProjects() {
  try {
    const res = await listProjects({ page: 1, pageSize: 200 })
    projects.value = res.items || []
  } catch {
    projects.value = []
  }
}

/** 根据项目 ID 获取项目名称 */
function projectName(id: string | null) {
  if (!id) return ''
  return projects.value.find(p => p.id === id)?.name || ''
}

/** 打开新建弹窗 */
async function openCreateDialog() {
  editingTask.value = null
  Object.assign(form, defaultForm())
  await loadProjects()
  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 loadProjects()

  Object.assign(form, {
    name: fullTask.name,
    description: fullTask.description,
    targetUrl: fullTask.targetUrl,
    method: fullTask.method,
    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,
    assertions: fullTask.assertions || [],
    uniqueFields: (fullTask.uniqueFields || []).map(r => ({ ...r })),
    projectId: fullTask.projectId || '',
    taskType: fullTask.steps && fullTask.steps.length ? 'transaction' : 'single',
    steps: (fullTask.steps || []).map(s => ({
      name: s.name || '',
      url: s.url || '',
      method: s.method || 'GET',
      headersText: s.headers ? JSON.stringify(s.headers, null, 2) : '',
      bodyText: s.body ? (typeof s.body === 'string' ? s.body : JSON.stringify(s.body, null, 2)) : '',
      timeout: s.timeout ?? 30,
      captureRules: (s.captureRules || []).map((cr: any) => ({
        key: cr.key || '',
        jsonPath: cr.json_path || cr.jsonPath || '',
      })),
    })),
    transactionFailPolicy: fullTask.transactionFailPolicy || 'fail_fast',
    transactionTimeout: fullTask.transactionTimeout ?? 60,
    // 场景增强回填
    scenarioType: fullTask.scenarioType || null,
    scenarioApis: (fullTask.scenarioApis || []).map((a: any) => ({
      name: a.name || '',
      method: a.method || 'GET',
      url: a.url || '',
      headers: a.headers ? JSON.stringify(a.headers, null, 2) : '',
      body: a.body ? (typeof a.body === 'string' ? a.body : JSON.stringify(a.body, null, 2)) : '',
      threadCount: a.threadCount ?? 1,
      assertions: a.assertions || [],
    })),
    loopType: fullTask.loopType || 'finite',
    loopCount: fullTask.loopCount ?? 1,
    synchronizingTimerEnabled: fullTask.synchronizingTimerEnabled ?? false,
    synchronizingTimerCount: fullTask.synchronizingTimerCount ?? 10,
    synchronizingTimerTimeout: fullTask.synchronizingTimerTimeout ?? 30000,
    errorAction: fullTask.errorAction || 'continue',
    thinkTimeEnabled: fullTask.thinkTimeEnabled ?? false,
    thinkTimeMin: fullTask.thinkTimeMin ?? 1000,
    thinkTimeMax: fullTask.thinkTimeMax ?? 3000,
    thinkTimeDistribution: fullTask.thinkTimeDistribution || 'constant',
    csvParameterizationEnabled: fullTask.csvParameterizationEnabled ?? false,
    csvContent: fullTask.csvContent || '',
    csvVariableMapping: fullTask.csvVariableMapping ? JSON.stringify(fullTask.csvVariableMapping, null, 2) : '',
  })
  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
  }
}

/** 场景类型切换 */
function onScenarioTypeChange(val: any) {
  const type = val || null
  // 场景模式强制使用并发模式，自动切换
  if (type) {
    form.mode = 'concurrent'
    form.taskType = 'single'
    form.steps = []
  }
  if (type === 'burst' && !form.synchronizingTimerCount) {
    form.synchronizingTimerCount = form.concurrency
  }
  if (type === 'endurance' && !form.scenarioApis.length && form.targetUrl) {
    addScenarioApi()
  }
}

/** 添加 endurance 场景接口 */
function addScenarioApi() {
  form.scenarioApis.push({
    name: '',
    method: 'GET',
    url: form.scenarioApis.length ? '' : form.targetUrl,
    headers: '',
    body: '',
    threadCount: form.concurrency || 1,
    assertions: [],
  })
}

/** 上移/下移场景接口 */
function moveScenarioApi(index: number, dir: number) {
  const target = index + dir
  if (target < 0 || target >= form.scenarioApis.length) return
  const arr = form.scenarioApis
  const tmp = arr[index]
  arr[index] = arr[target]
  arr[target] = tmp
}

/** 打开 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) {
  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 = ''
  }

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

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

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

// ==================== 事务步骤 ====================

/** 添加步骤 */
function addStep() {
  form.steps.push(defaultStep())
}

/** 上移/下移步骤 */
function moveStep(index: number, dir: number) {
  const target = index + dir
  if (target < 0 || target >= form.steps.length) return
  const arr = form.steps
  const tmp = arr[index]
  arr[index] = arr[target]
  arr[target] = tmp
}

/** 收集指定步骤之前所有步骤捕获的变量名（用于提示 + 去重） */
function capturedKeys(sIdx: number): string[] {
  const keys: string[] = []
  for (let i = 0; i < sIdx; i++) {
    for (const cr of form.steps[i].captureRules) {
      if (cr.key && !keys.includes(cr.key)) keys.push(cr.key)
    }
  }
  return keys
}

/** 将捕获变量插入到当前步骤的 URL / Body 光标处 */
function insertCapturedVar(sIdx: number, key: string) {
  const step = form.steps[sIdx]
  if (!step) return
  const token = '{{captured.' + key + '}}'
  // 优先插入 Body（大多数场景变量用在请求体），无 Body 时插入 URL
  if (step.bodyText.length) {
    step.bodyText += token
  } else {
    step.url += token
  }
}

/** 提取后端校验错误的可读信息（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 || ''
}

/** 安全解析 JSON，解析失败时返回 null 而不是抛异常 */
function safeJsonParse(str: string): any {
  try {
    return JSON.parse(str)
  } catch {
    return null
  }
}

/** XLSX 导入：解析第一个工作表为 CSV 文本（第一行为列名） */
function handleXlsxUpload(file: File): boolean {
  const reader = new FileReader()
  reader.onload = (e: any) => {
    try {
      const wb = XLSX.read(e.target.result, { type: 'array' })
      const ws = wb.Sheets[wb.SheetNames[0]]
      if (!ws) {
        ElMessage.error('文件为空，请检查模板内容')
        return
      }
      const rows = XLSX.utils.sheet_to_json<any[]>(ws, { header: 1, defval: '' })
      const nonEmpty = rows.filter((r) => r.length && r.some((c) => String(c).trim() !== ''))
      if (nonEmpty.length < 2) {
        ElMessage.warning('数据不足两行（至少需要表头 + 1 行数据）')
        return
      }
      // 表头与数据单元格统一转为字符串，避免数字/空值导致 CSV 错位
      const csvLines = nonEmpty.map((r) => r.map((c) => String(c).trim()).join(','))
      form.csvContent = csvLines.join('\n')
      ElMessage.success(`已导入 ${nonEmpty.length - 1} 行数据`)
    } catch {
      ElMessage.error('解析 XLSX 失败，请使用下载的模板格式')
    }
  }
  reader.readAsArrayBuffer(file)
  return false
}

/** 下载 CSV 用户数据模板（xlsx） */
function downloadCsvTemplate() {
  const a = document.createElement('a')
  a.href = '/模板_CSV用户数据.xlsx'
  a.download = '模板_CSV用户数据.xlsx'
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
}

/** 保存任务 */
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,
      projectId: form.projectId || null,
      targetUrl: form.targetUrl,
      method: form.method,
      headers: form.headers ? JSON.parse(form.headers) : null,
      body: form.body ? JSON.parse(form.body) : 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,
      assertions: [],
      uniqueFields: form.uniqueFields.length > 0 ? form.uniqueFields : null,
      steps: form.taskType === 'transaction' ? form.steps.map(s => ({
        name: s.name,
        url: s.url,
        method: s.method,
        headers: s.headersText ? safeJsonParse(s.headersText) : null,
        // body 解析失败时按原始字符串提交（body 可能是含 {{captured.*}} 的非 JSON 模板）
        body: s.bodyText ? (safeJsonParse(s.bodyText) ?? s.bodyText) : null,
        timeout: s.timeout ?? 30,
        captureRules: s.captureRules.filter(cr => cr.key && cr.jsonPath).map(cr => ({
          key: cr.key,
          json_path: cr.jsonPath,
        })),
      })) : null,
      transactionFailPolicy: form.taskType === 'transaction' ? form.transactionFailPolicy : null,
      transactionTimeout: form.taskType === 'transaction' ? form.transactionTimeout : null,
      // 场景增强字段
      scenarioType: form.scenarioType,
      scenarioApis: form.scenarioType === 'endurance' ? form.scenarioApis.map(a => ({
        name: a.name,
        method: a.method || 'GET',
        url: a.url,
        headers: a.headers ? safeJsonParse(a.headers) : null,
        body: a.body ? (safeJsonParse(a.body) ?? a.body) : null,
        threadCount: a.threadCount || 1,
        assertions: a.assertions || [],
      })) : null,
      loopType: form.scenarioType === 'burst' ? form.loopType : null,
      loopCount: form.scenarioType === 'burst' && form.loopType === 'finite' ? form.loopCount : null,
      synchronizingTimerEnabled: form.scenarioType === 'burst' ? form.synchronizingTimerEnabled : null,
      synchronizingTimerCount: form.scenarioType === 'burst' && form.synchronizingTimerEnabled ? form.synchronizingTimerCount : null,
      synchronizingTimerTimeout: form.scenarioType === 'burst' && form.synchronizingTimerEnabled ? form.synchronizingTimerTimeout : null,
      errorAction: form.scenarioType === 'burst' ? form.errorAction : null,
      thinkTimeEnabled: form.scenarioType === 'endurance' ? form.thinkTimeEnabled : null,
      thinkTimeMin: form.scenarioType === 'endurance' && form.thinkTimeEnabled ? form.thinkTimeMin : null,
      thinkTimeMax: form.scenarioType === 'endurance' && form.thinkTimeEnabled ? form.thinkTimeMax : null,
      thinkTimeDistribution: form.scenarioType === 'endurance' && form.thinkTimeEnabled ? form.thinkTimeDistribution : null,
      csvParameterizationEnabled: form.csvParameterizationEnabled,
      csvContent: form.csvParameterizationEnabled ? form.csvContent : null,
      csvVariableMapping: form.csvParameterizationEnabled && form.csvVariableMapping
        ? safeJsonParse(form.csvVariableMapping) : 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)
    // 非阻塞返回，立即跳转监控页携带 executionId
    if (res.executionId) {
      router.push({
        path: '/performance/monitor',
        query: { taskId: task.id, executionId: res.executionId },
      })
    } else {
      // 兼容旧版（无 executionId 时）
      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 scenarioLabel(scenario: string | null) {
  const map: Record<string, string> = { burst: '瞬时并发', endurance: '长稳压测' }
  return scenario ? (map[scenario] || scenario) : '标准'
}

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

/** 模板变量展示文本 */
const tplVar = '{{captured.key}}'

/** 捕获变量标签文本 */
function capturedToken(key: string) {
  return '{{captured.' + key + '}}'
}

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;
}

/* 唯一性字段配置 */
.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;
}

/* 事务步骤编辑器 */
.txn-tip {
  font-size: 12px;
  color: #909399;
  line-height: 1.7;
  margin-bottom: 12px;
  padding: 8px 12px;
  background: #f5f7fa;
  border-radius: 4px;
  border-left: 3px solid #409eff;
}
.txn-tip code {
  background: #e6f1ff;
  padding: 1px 5px;
  border-radius: 3px;
  color: #409eff;
  font-size: 12px;
}
.txn-step {
  border: 1px solid #e4e7ed;
  border-radius: 6px;
  padding: 12px;
  margin-bottom: 12px;
  background: #fafafa;
}
.txn-step-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
.txn-step-actions {
  margin-left: auto;
  display: flex;
  gap: 4px;
}
.capture-row {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-bottom: 6px;
}
.capture-hint {
  min-height: 28px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}
</style>
