<template>
  <div class="task-mgmt-page">
    <!-- 统计条 -->
    <header class="tm-stats">
      <div class="tm-stat">
        <span class="tm-stat-label">任务总数</span>
        <span class="tm-stat-value"><b>{{ rows.length }}</b></span>
      </div>
      <div class="tm-stat-sep" aria-hidden="true" />
      <div class="tm-stat">
        <span class="tm-stat-label">等待</span>
        <span class="tm-stat-value"><b>{{ counts.waiting }}</b></span>
      </div>
      <div class="tm-stat">
        <span class="tm-stat-label">执行中</span>
        <span class="tm-stat-value tm-ok"><b>{{ counts.running }}</b></span>
      </div>
      <div class="tm-stat">
        <span class="tm-stat-label">已完成</span>
        <span class="tm-stat-value"><b>{{ counts.done }}</b></span>
      </div>
      <div class="tm-stat">
        <span class="tm-stat-label">异常</span>
        <span class="tm-stat-value" :class="{ 'tm-danger': counts.error > 0 }"><b>{{ counts.error }}</b></span>
      </div>
      <div class="tm-stat">
        <span class="tm-stat-label">超时</span>
        <span class="tm-stat-value" :class="{ 'tm-warn': counts.overdue > 0 }"><b>{{ counts.overdue }}</b></span>
      </div>

      <div class="tm-stats-actions">
        <span class="tm-live" :class="{ 'is-live': autoRefresh }" @click="autoRefresh = !autoRefresh">
          {{ autoRefresh ? '自动刷新' : '已暂停' }}
        </span>
        <el-button :icon="Refresh" :loading="loading" @click="reload">刷新</el-button>
        <el-button type="primary" :icon="Plus" @click="openCreate">新建任务</el-button>
      </div>
    </header>

    <el-alert
      v-if="!online"
      type="warning"
      show-icon
      :closable="false"
      class="tm-offline"
      title="SimpleLite 未连接：以下为平台最近一次快照；取消/重发/新建等操作需 SimpleLite 在线，离线执行会提示失败。"
      :description="lastSyncAt ? `最近同步：${formatTime(lastSyncAt)}` : '暂无同步记录'"
    />

    <!-- 筛选条 -->
    <div class="tm-toolbar">
      <el-input
        v-model="search"
        clearable
        placeholder="搜索任务号 / 单号 / 站点 / 车辆 / 进程"
        class="tm-search"
        :prefix-icon="Search"
      />
      <el-select v-model="statusFilter" clearable placeholder="状态" class="tm-status">
        <el-option v-for="opt in statusOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
      </el-select>
      <el-date-picker
        v-model="dateRange"
        type="daterange"
        value-format="YYYY-MM-DD"
        start-placeholder="下发起"
        end-placeholder="下发止"
        class="tm-date"
        :shortcuts="dateShortcuts"
        unlink-panels
      />
      <el-checkbox v-model="includeFinished">含已完成</el-checkbox>
      <el-checkbox v-model="includeAborted">含取消/异常</el-checkbox>
      <span class="tm-count">{{ filteredRows.length }} / {{ rows.length }}</span>
    </div>

    <!-- 快捷芯片 -->
    <div class="tm-chips" role="tablist">
      <button
        v-for="chip in statusChips"
        :key="chip.key"
        type="button"
        class="tm-chip"
        :class="{ 'is-active': quickStatus === chip.key }"
        @click="quickStatus = chip.key"
      >
        {{ chip.label }}<b>{{ chip.count }}</b>
      </button>
    </div>

    <!-- 任务表 -->
    <div v-loading="loading" class="tm-table-wrap">
      <el-table
        :data="filteredRows"
        stripe
        height="100%"
        highlight-current-row
        :row-class-name="rowClassName"
        empty-text="暂无匹配任务"
        @row-click="openDetail"
      >
        <el-table-column label="任务号" min-width="150" show-overflow-tooltip>
          <template #default="{ row }">
            <div class="tm-idcell">
              <span class="tm-id">{{ row.id }}</span>
              <span v-if="row.taskId" class="tm-taskid">单号 {{ row.taskId }}</span>
            </div>
          </template>
        </el-table-column>
        <el-table-column label="取货点" min-width="130" show-overflow-tooltip>
          <template #default="{ row }">{{ row.srcLabel || `站点 ${row.srcSiteId}` }}</template>
        </el-table-column>
        <el-table-column label="放货点" min-width="130" show-overflow-tooltip>
          <template #default="{ row }">{{ row.dstLabel || `站点 ${row.dstSiteId}` }}</template>
        </el-table-column>
        <el-table-column label="车辆" width="110" show-overflow-tooltip>
          <template #default="{ row }">
            <span v-if="row.carName || row.carId">{{ row.carName || `#${row.carId}` }}</span>
            <span v-else class="muted">未分配</span>
          </template>
        </el-table-column>
        <el-table-column label="状态" width="96" align="center">
          <template #default="{ row }">
            <el-tag size="small" :type="statusTagType(row.statusCode)" effect="plain">
              {{ row.status || row.statusCode }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="priority" label="优先级" width="76" align="center" sortable />
        <el-table-column label="下发时间" width="160" sortable :sort-method="(a, b) => sortByTime(a.createTime, b.createTime)">
          <template #default="{ row }">{{ formatTime(row.createTime) }}</template>
        </el-table-column>
        <el-table-column label="结束时间" width="160">
          <template #default="{ row }">{{ formatTime(row.finishTime) }}</template>
        </el-table-column>
        <el-table-column label="卡住原因" min-width="120" show-overflow-tooltip>
          <template #default="{ row }">
            <span v-if="row.stuckReason" class="tm-stuck">{{ row.stuckReason }}</span>
            <span v-else class="muted">—</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="240" fixed="right">
          <template #default="{ row }">
            <div class="tm-ops">
              <el-button link type="primary" size="small" @click.stop="openDetail(row)">详情</el-button>
              <el-button link type="warning" size="small" :disabled="!canCancel(row) || acting" @click.stop="runAction(row, 'cancel')">取消</el-button>
              <el-button link type="primary" size="small" :disabled="acting" @click.stop="runAction(row, 'resend')">重发</el-button>
              <el-dropdown trigger="click" @command="(cmd: string) => onMore(row, cmd)">
                <el-button link type="info" size="small" @click.stop>
                  更多<el-icon class="el-icon--right"><ArrowDown /></el-icon>
                </el-button>
                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item command="force-complete" :disabled="!canCancel(row)">强制完成</el-dropdown-item>
                    <el-dropdown-item command="pause" :disabled="!canPause(row)">暂停</el-dropdown-item>
                    <el-dropdown-item command="resume" :disabled="!canResume(row)">恢复</el-dropdown-item>
                    <el-dropdown-item command="change-car" :disabled="!canChangeCar(row)">换车</el-dropdown-item>
                    <el-dropdown-item command="priority" divided>改优先级</el-dropdown-item>
                    <el-dropdown-item command="locate" :disabled="!row.carId">地图定位</el-dropdown-item>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
            </div>
          </template>
        </el-table-column>
      </el-table>
    </div>

    <p class="tm-footnote">
      数据来自 CDM 搬运任务投影（StandardScene TransportDelivery）。超时 = 下发超过 30 分钟仍未结束。
    </p>

    <!-- 详情抽屉 -->
    <el-drawer
      v-model="detailVisible"
      modal-class="tm-detail-drawer-modal"
      title="任务详情"
      size="420px"
      :with-header="true"
    >
      <div v-if="detailRow" class="tm-detail">
        <div v-for="item in detailItems" :key="item.k" class="tm-detail-row">
          <span class="tm-detail-k">{{ item.k }}</span>
          <span class="tm-detail-v" :class="item.cls">{{ item.v }}</span>
        </div>
      </div>
    </el-drawer>

    <!-- 新建任务弹窗 -->
    <el-dialog v-model="createVisible" title="新建搬运任务" width="460px" destroy-on-close append-to-body>
      <el-form label-width="80px" class="tm-create-form">
        <el-form-item label="取货点">
          <el-select v-model="createForm.src" filterable clearable placeholder="选择取货站点" class="tm-create-select">
            <el-option v-for="s in siteOptions" :key="s.value" :label="s.label" :value="s.value" />
          </el-select>
        </el-form-item>
        <el-form-item label="放货点">
          <el-select v-model="createForm.dst" filterable clearable placeholder="选择放货站点" class="tm-create-select">
            <el-option v-for="s in siteOptions" :key="s.value" :label="s.label" :value="s.value" />
          </el-select>
        </el-form-item>
        <el-form-item label="优先级">
          <el-input-number v-model="createForm.priority" :min="0" :max="999" />
        </el-form-item>
        <el-form-item label="车型">
          <el-input v-model="createForm.carType" placeholder="默认 Car" />
        </el-form-item>
        <el-form-item label="外部单号">
          <el-input v-model="createForm.taskId" placeholder="可选，外部系统单号" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="createVisible = false">取消</el-button>
        <el-button type="primary" :loading="creating" :disabled="!createForm.src && !createForm.dst" @click="submitCreate">
          下发
        </el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ArrowDown, Plus, Refresh, Search } from '@element-plus/icons-vue'
import {
  cancelDelivery,
  changeCarDelivery,
  createDelivery,
  fetchCdmTaskFeed,
  forceCompleteDelivery,
  pauseDelivery,
  resendDelivery,
  resumeDelivery,
  setDeliveryPriority
} from '@/api/delivery'
import { listSites } from '@/api/projection'
import type { DeliveryTask } from '@/types/delivery'
import { dateShortcuts, formatTime, parseTime, sortByTime } from '@/utils/dateTime'

type QuickKey = 'all' | 'waiting' | 'running' | 'done' | 'aborted' | 'error'
type TagType = 'success' | 'warning' | 'danger' | 'info' | 'primary'
type MoreCmd = 'force-complete' | 'pause' | 'resume' | 'change-car' | 'priority' | 'locate'

const router = useRouter()

const rows = ref<DeliveryTask[]>([])
const loading = ref(false)
const acting = ref(false)
const autoRefresh = ref(true)
const online = ref(true)
const lastSyncAt = ref<string | null>(null)

const search = ref('')
const statusFilter = ref<string | null>(null)
const quickStatus = ref<QuickKey>('all')
const dateRange = ref<[string, string] | null>(null)
const includeFinished = ref(true)
const includeAborted = ref(true)

const detailVisible = ref(false)
const detailRow = ref<DeliveryTask | null>(null)

const createVisible = ref(false)
const creating = ref(false)
const createForm = reactive<{ src?: number; dst?: number; priority: number; carType: string; taskId: string }>({
  src: undefined,
  dst: undefined,
  priority: 0,
  carType: '',
  taskId: ''
})
const siteOptions = ref<{ value: number; label: string }[]>([])

let pollTimer: ReturnType<typeof setInterval> | null = null

const statusOptions = [
  { value: 'Waiting', label: '等待 / 已下发' },
  { value: 'Fetching', label: '取货中' },
  { value: 'Putting', label: '放货中' },
  { value: 'Finished', label: '已完成' },
  { value: 'Canceled', label: '已取消' },
  { value: 'Terminated', label: '已终止' },
  { value: 'Error', label: '异常' },
  { value: 'Suspended', label: '挂起' }
]

function isTerminal(code: string) {
  return ['Finished', 'Canceled', 'Terminated', 'Error'].includes(code)
}
function isRunning(code: string) {
  return code === 'Fetching' || code === 'Putting'
}
function isAborted(code: string) {
  return code === 'Canceled' || code === 'Terminated'
}

function matchesQuick(row: DeliveryTask, key: QuickKey): boolean {
  const c = row.statusCode
  switch (key) {
    case 'waiting': return c === 'Waiting' || c === 'Suspended'
    case 'running': return isRunning(c)
    case 'done': return c === 'Finished'
    case 'aborted': return isAborted(c)
    case 'error': return c === 'Error'
    default: return true
  }
}

function inDateRange(row: DeliveryTask): boolean {
  if (!dateRange.value) return true
  const t = parseTime(row.createTime)
  if (t == null) return false
  const [from, to] = dateRange.value
  return t >= Date.parse(`${from}T00:00:00`) && t <= Date.parse(`${to}T23:59:59.999`)
}

const filteredRows = computed(() => {
  const q = search.value.trim().toLowerCase()
  const tokens = q ? q.split(/\s+/).filter(Boolean) : []
  return rows.value.filter((row) => {
    if (!includeFinished.value && row.statusCode === 'Finished') return false
    if (!includeAborted.value && (isAborted(row.statusCode) || row.statusCode === 'Error')) return false
    if (statusFilter.value && row.statusCode !== statusFilter.value) return false
    if (!matchesQuick(row, quickStatus.value)) return false
    if (!inDateRange(row)) return false
    if (!tokens.length) return true
    const hay = [
      row.id, row.taskId ?? '', row.srcLabel, row.dstLabel, row.status, row.statusCode,
      row.carName ?? '', String(row.carId ?? ''), row.missionName, row.missionTypeName
    ].join(' ').toLowerCase()
    return tokens.every((tok) => hay.includes(tok))
  })
})

const counts = computed(() => ({
  waiting: rows.value.filter((r) => matchesQuick(r, 'waiting')).length,
  running: rows.value.filter((r) => matchesQuick(r, 'running')).length,
  done: rows.value.filter((r) => r.statusCode === 'Finished').length,
  error: rows.value.filter((r) => r.statusCode === 'Error').length,
  overdue: rows.value.filter((r) => r.overdue).length
}))

const statusChips = computed(() => {
  const base = rows.value
  const count = (key: QuickKey) => base.filter((r) => matchesQuick(r, key)).length
  return [
    { key: 'all' as const, label: '全部', count: base.length },
    { key: 'waiting' as const, label: '等待', count: count('waiting') },
    { key: 'running' as const, label: '执行中', count: count('running') },
    { key: 'done' as const, label: '已完成', count: count('done') },
    { key: 'aborted' as const, label: '取消/终止', count: count('aborted') },
    { key: 'error' as const, label: '异常', count: count('error') }
  ]
})

function statusTagType(code: string): TagType {
  switch (code) {
    case 'Fetching':
    case 'Putting': return 'success'
    case 'Waiting':
    case 'Suspended': return 'primary'
    case 'Finished': return 'info'
    case 'Canceled':
    case 'Terminated': return 'warning'
    case 'Error': return 'danger'
    default: return 'info'
  }
}

function rowClassName({ row }: { row: DeliveryTask }) {
  return row.overdue ? 'is-overdue-row' : ''
}

function canCancel(row: DeliveryTask) { return !isTerminal(row.statusCode) }
function canPause(row: DeliveryTask) { return isRunning(row.statusCode) }
function canResume(row: DeliveryTask) { return row.statusCode === 'Terminated' || row.statusCode === 'Suspended' }
function canChangeCar(row: DeliveryTask) {
  return row.statusCode === 'Suspended' || row.statusCode === 'Waiting'
}

async function reload() {
  loading.value = true
  try {
    const feed = await fetchCdmTaskFeed(2000)
    rows.value = feed.tasks
    online.value = feed.online
    lastSyncAt.value = feed.lastSyncAt
  } catch (err) {
    ElMessage.error(`加载任务失败：${(err as Error).message}`)
  } finally {
    loading.value = false
  }
}

const actionLabels: Record<string, string> = {
  cancel: '取消任务',
  resend: '重发任务',
  'force-complete': '强制完成',
  pause: '暂停任务',
  resume: '恢复任务',
  'change-car': '换车'
}

async function runAction(row: DeliveryTask, action: string) {
  try {
    await ElMessageBox.confirm(
      `确定对任务 ${row.id}（${row.srcLabel} → ${row.dstLabel}）执行「${actionLabels[action]}」？`,
      '确认操作',
      { type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }
    )
  } catch {
    return
  }
  acting.value = true
  try {
    if (action === 'cancel') await cancelDelivery(row.id)
    else if (action === 'resend') await resendDelivery(row.id)
    else if (action === 'force-complete') await forceCompleteDelivery(row.id)
    else if (action === 'pause') await pauseDelivery(row.id)
    else if (action === 'resume') await resumeDelivery(row.id)
    else if (action === 'change-car') await changeCarDelivery(row.id)
    ElMessage.success(`${actionLabels[action]} 已提交`)
    await reload()
  } catch (err) {
    ElMessage.error(`${actionLabels[action]} 失败：${(err as Error).message}`)
  } finally {
    acting.value = false
  }
}

async function onMore(row: DeliveryTask, cmd: string) {
  const c = cmd as MoreCmd
  if (c === 'locate') { locateOnMap(row); return }
  if (c === 'priority') { await editPriority(row); return }
  await runAction(row, c)
}

async function editPriority(row: DeliveryTask) {
  try {
    const { value } = await ElMessageBox.prompt('设置任务优先级（数值越大越优先）', '改优先级', {
      inputValue: String(row.priority ?? 0),
      inputPattern: /^-?\d+$/,
      inputErrorMessage: '请输入整数',
      confirmButtonText: '保存',
      cancelButtonText: '取消'
    })
    acting.value = true
    await setDeliveryPriority(row.id, Number(value))
    ElMessage.success('优先级已更新')
    await reload()
  } catch (err) {
    if (err !== 'cancel') ElMessage.error(`更新失败：${(err as Error).message}`)
  } finally {
    acting.value = false
  }
}

function locateOnMap(row: DeliveryTask) {
  if (!row.carId) { ElMessage.info('该任务尚未分配车辆'); return }
  void router.push({ path: '/admin/map-monitor', query: { focusKind: 'car', focusId: String(row.carId) } })
}

function openDetail(row: DeliveryTask) {
  detailRow.value = row
  detailVisible.value = true
}

const detailItems = computed(() => {
  const r = detailRow.value
  if (!r) return [] as { k: string; v: string; cls?: string }[]
  return [
    { k: '任务号', v: r.id },
    { k: '外部单号', v: r.taskId || '—' },
    { k: '状态', v: r.status || r.statusCode },
    { k: '取货点', v: r.srcLabel || String(r.srcSiteId) },
    { k: '放货点', v: r.dstLabel || String(r.dstSiteId) },
    { k: '车辆', v: r.carName || (r.carId ? `#${r.carId}` : '未分配') },
    { k: '优先级', v: String(r.priority ?? 0) },
    { k: '进程', v: r.missionName || r.missionTypeName || '—' },
    { k: '下发时间', v: formatTime(r.createTime) },
    { k: '开始时间', v: formatTime(r.startTime) },
    { k: '结束时间', v: formatTime(r.finishTime) },
    { k: '卡住原因', v: r.stuckReason || '—', cls: r.stuckReason ? 'tm-danger-text' : undefined },
    { k: '是否超时', v: r.overdue ? '是' : '否', cls: r.overdue ? 'tm-warn-text' : undefined }
  ]
})

async function loadSites() {
  try {
    const sites = await listSites()
    siteOptions.value = sites
      .map((s) => {
        const num = parseInt(String(s.id).replace(/\D/g, ''), 10)
        return { value: num, label: `${num} · ${s.name || '未命名'}` }
      })
      .filter((o) => Number.isFinite(o.value))
      .sort((a, b) => a.value - b.value)
  } catch {
    siteOptions.value = []
  }
}

function openCreate() {
  createForm.src = undefined
  createForm.dst = undefined
  createForm.priority = 0
  createForm.carType = ''
  createForm.taskId = ''
  if (!siteOptions.value.length) void loadSites()
  createVisible.value = true
}

async function submitCreate() {
  if (!createForm.src && !createForm.dst) {
    ElMessage.warning('取货点或放货点至少填一个')
    return
  }
  creating.value = true
  try {
    const res = await createDelivery({
      src: createForm.src ?? 0,
      dst: createForm.dst ?? 0,
      priority: createForm.priority || 0,
      carType: createForm.carType.trim() || undefined,
      taskId: createForm.taskId.trim() || undefined
    })
    ElMessage.success(`任务已下发${res.id ? `（${res.id}）` : ''}`)
    createVisible.value = false
    await reload()
  } catch (err) {
    ElMessage.error(`下发失败：${(err as Error).message}`)
  } finally {
    creating.value = false
  }
}

onMounted(() => {
  void reload()
  void loadSites()
  pollTimer = setInterval(() => {
    if (autoRefresh.value && !createVisible.value && !acting.value) void reload()
  }, 6000)
})

onUnmounted(() => {
  if (pollTimer) clearInterval(pollTimer)
})
</script>

<style scoped>
.task-mgmt-page {
  display: flex;
  flex-direction: column;
  gap: 10px;
  height: calc(100vh - 56px - 36px - 32px);
  min-height: 0;
  color: var(--mg-text-light);
}

.tm-stats {
  flex: none;
  display: flex;
  align-items: center;
  gap: 26px;
  min-height: 54px;
  padding: 8px 18px;
  border-radius: 10px;
  background: rgba(var(--mg-bg-card-rgb), 0.94);
  border: 1px solid var(--mg-veil-border);
  overflow-x: auto;
}
.tm-stat { display: flex; flex-direction: column; gap: 3px; min-width: 56px; }
.tm-stat-label { font-size: 11px; color: var(--mg-text-muted); white-space: nowrap; }
.tm-stat-value {
  font-family: var(--mg-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 17px;
  font-weight: 650;
  line-height: 1.1;
}
.tm-stat-value.tm-ok b { color: var(--mg-status-success); }
.tm-stat-value.tm-warn b { color: var(--mg-status-warning); }
.tm-stat-value.tm-danger b { color: var(--mg-status-danger); }
.tm-stat-sep { width: 1px; height: 30px; background: var(--mg-veil-border); flex: none; }

.tm-stats-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; flex: none; }
.tm-live {
  font-size: 12px;
  color: var(--mg-text-muted);
  cursor: pointer;
  padding: 2px 10px;
  border-radius: 999px;
  border: 1px solid var(--mg-veil-border);
  user-select: none;
}
.tm-live.is-live { color: var(--mg-status-success); border-color: rgba(var(--mg-status-success-rgb, 34,197,94), 0.4); }

.tm-toolbar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 8px 12px;
  border-radius: 10px;
  background: rgba(var(--mg-bg-card-rgb), 0.94);
  border: 1px solid var(--mg-veil-border);
}
.tm-search { width: min(280px, 100%); }
.tm-status { width: 130px; }
.tm-date { width: 250px; }
.tm-count {
  margin-left: auto;
  font-size: 12px;
  color: var(--mg-text-muted);
  font-variant-numeric: tabular-nums;
}

.tm-chips { flex: none; display: flex; gap: 8px; flex-wrap: wrap; }
.tm-chip {
  appearance: none;
  border: 1px solid var(--mg-veil-border);
  background: rgba(var(--mg-bg-card-rgb), 0.9);
  color: var(--mg-text-muted);
  border-radius: 999px;
  padding: 4px 12px;
  font-size: 12px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.tm-chip b { font-family: var(--mg-font-mono); font-variant-numeric: tabular-nums; color: var(--mg-text-light); }
.tm-chip:hover { color: var(--mg-text-light); background: var(--mg-veil-2); }
.tm-chip.is-active {
  color: var(--mg-primary);
  border-color: rgba(var(--mg-primary-rgb), 0.45);
  background: rgba(var(--mg-primary-rgb), 0.12);
}

.tm-table-wrap {
  flex: 1 1 0;
  min-height: 0;
  border-radius: 10px;
  background: rgba(var(--mg-bg-card-rgb), 0.94);
  border: 1px solid var(--mg-veil-border);
  overflow: hidden;
  padding: 4px;
}
.tm-table-wrap :deep(.is-overdue-row) { --el-table-tr-bg-color: rgba(245, 158, 11, 0.08); }
.tm-table-wrap :deep(.el-table__row) { cursor: pointer; }

.tm-idcell { display: flex; flex-direction: column; line-height: 1.25; }
.tm-id { font-family: var(--mg-font-mono); font-size: 12px; }
.tm-taskid { font-size: 11px; color: var(--mg-text-muted); }
.tm-stuck { color: var(--mg-status-danger); }
.muted { color: var(--mg-text-muted); }

/* 操作列：flex 均匀间距（el-dropdown 会打断 el-button 相邻 margin，导致「重发」和「更多」贴住）；
   本主题把 primary 的 link 按钮渲染成实心紫底+白字，需强制成透明底+彩色文字，四个按钮风格统一 */
.tm-ops {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: nowrap;
}
.tm-ops :deep(.el-button) { margin: 0; }
.tm-ops :deep(.el-button.is-link) {
  margin-left: 0;
  padding: 0;
  height: auto;
  min-height: 0;
  border: none;
  background: transparent !important;
  box-shadow: none !important;
  vertical-align: middle;
  font-size: 13px;
}
.tm-ops :deep(.el-button--primary.is-link) { color: var(--mg-primary) !important; }
.tm-ops :deep(.el-button--warning.is-link) { color: var(--mg-status-warning) !important; }
.tm-ops :deep(.el-button--info.is-link) { color: var(--mg-text-muted) !important; }
.tm-ops :deep(.el-button.is-link.is-disabled) { color: var(--mg-text-muted) !important; opacity: 0.45; }
.tm-ops :deep(.el-button.is-link:not(.is-disabled):hover) { text-decoration: underline; }
.tm-ops :deep(.el-dropdown) { line-height: 1; }

.tm-offline { flex: none; }
.tm-footnote { flex: none; margin: 0; font-size: 12px; color: var(--mg-text-muted); }

.tm-detail { display: flex; flex-direction: column; gap: 2px; }
.tm-detail-row {
  display: grid;
  grid-template-columns: 88px 1fr;
  gap: 8px;
  padding: 8px 4px;
  border-bottom: 1px solid var(--mg-veil-border);
}
.tm-detail-k { color: var(--mg-text-muted); font-size: 13px; }
.tm-detail-v { font-size: 13px; word-break: break-all; }
.tm-detail-v.tm-danger-text { color: var(--mg-status-danger); }
.tm-detail-v.tm-warn-text { color: var(--mg-status-warning); }

.tm-create-form { padding-right: 8px; }
.tm-create-select { width: 100%; }

@media (max-width: 960px) {
  .tm-search, .tm-date { width: 100%; }
}
</style>

