<template>
  <div class="space-y-4">
    <a-page-header title="工单管理" sub-title="提交需求与问题反馈，跟踪处理进度">
      <template #extra>
        <a-space>
          <a-input
            v-model:value="keywords"
            allow-clear
            placeholder="搜索工单标题"
            class="w-52"
            @press-enter="doSearch"
          />
          <a-button type="primary" @click="openCreate">
            <template #icon><PlusOutlined /></template>
            提交工单
          </a-button>
        </a-space>
      </template>
    </a-page-header>

    <!-- 统计卡片 -->
    <a-row :gutter="[16, 16]">
      <a-col :xs="12" :sm="6" v-for="s in statsCards" :key="s.label">
        <div class="stat-card" :class="s.colorClass">
          <div class="stat-value">{{ s.value }}</div>
          <div class="stat-label">{{ s.label }}</div>
        </div>
      </a-col>
    </a-row>

    <!-- 主内容 -->
    <a-card :bordered="false" class="card">
      <!-- 筛选栏 -->
      <div class="filter-bar">
        <a-segmented
          v-model:value="statusFilter"
          :options="statusOptions"
          @change="doSearch"
        />
        <a-space class="filter-right">
          <a-select
            v-model:value="appFilter"
            allow-clear
            placeholder="全部应用"
            style="min-width: 160px"
            @change="doSearch"
          >
            <a-select-option v-for="app in myApps" :key="app.productId" :value="app.productId">
              {{ app.siteName || app.productName }}
            </a-select-option>
          </a-select>
          <a-select
            v-model:value="categoryFilter"
            allow-clear
            placeholder="全部分类"
            style="min-width: 120px"
            @change="doSearch"
          >
            <a-select-option v-for="c in categoryOptions" :key="c.value" :value="c.value">
              {{ c.label }}
            </a-select-option>
          </a-select>
        </a-space>
      </div>

      <a-alert v-if="error" class="mb-4" show-icon type="error" :message="error" />

      <!-- 工单列表 -->
      <a-spin :spinning="loading">
        <div v-if="!loading && tickets.length === 0" class="empty-wrap">
          <a-empty description="暂无工单">
            <a-button type="primary" @click="openCreate">立即提交工单</a-button>
          </a-empty>
        </div>

        <div v-else class="ticket-list">
          <div
            v-for="ticket in tickets"
            :key="ticket.ticketId"
            class="ticket-item"
            :class="{ 'has-unread': ticket.hasUnread }"
            @click="openDetail(ticket)"
          >
            <div class="ticket-item-left">
              <div class="ticket-badge-row">
                <a-tag :color="statusColor(ticket.status)" class="status-tag">
                  {{ statusLabel(ticket.status) }}
                </a-tag>
                <a-tag :color="priorityColor(ticket.priority)" class="priority-tag">
                  {{ priorityLabel(ticket.priority) }}
                </a-tag>
                <a-tag class="category-tag">{{ categoryLabel(ticket.category) }}</a-tag>
                <span v-if="ticket.hasUnread" class="unread-dot" />
              </div>
              <div class="ticket-title">{{ ticket.title }}</div>
              <div class="ticket-meta">
                <span class="meta-app">📦 {{ ticket.productName || `应用 #${ticket.productId}` }}</span>
                <span class="meta-no">{{ ticket.ticketNo }}</span>
                <span v-if="ticket.attachments?.length" class="meta-attach">📎 {{ ticket.attachments.length }}个附件</span>
                <span class="meta-time">{{ formatTime(ticket.createTime) }}</span>
              </div>
            </div>
            <div class="ticket-item-right">
              <div v-if="ticket.assigneeName" class="assignee">
                <a-avatar :size="24" :src="ticket.assigneeAvatar">
                  <template #icon><UserOutlined /></template>
                </a-avatar>
                <span class="assignee-name">{{ ticket.assigneeName }}</span>
              </div>
              <div v-else class="assignee-pending">待分配</div>
              <div class="reply-count">
                <MessageOutlined />
                <span>{{ ticket.replyCount }}</span>
              </div>
            </div>
          </div>
        </div>
      </a-spin>

      <!-- 分页 -->
      <div v-if="total > pageSize" class="pagination-wrap">
        <a-pagination
          v-model:current="currentPage"
          :total="total"
          :page-size="pageSize"
          show-quick-jumper
          :show-total="(t: number) => `共 ${t} 条`"
          @change="loadTickets"
        />
      </div>
    </a-card>

    <!-- ========= 提交工单弹窗 ========= -->
    <a-modal
      v-model:open="showCreate"
      title="提交工单"
      width="640px"
      :confirm-loading="submitLoading"
      ok-text="提交"
      @ok="handleSubmit"
      @cancel="showCreate = false"
    >
      <a-form :model="form" :rules="rules" ref="formRef" layout="vertical" class="mt-2">
        <a-row :gutter="16">
          <a-col :span="24">
            <a-form-item label="工单标题" name="title">
              <a-input v-model:value="form.title" placeholder="简短描述您的问题或需求" allow-clear />
            </a-form-item>
          </a-col>
        </a-row>
        <a-row :gutter="16">
          <a-col :span="12">
            <a-form-item label="关联应用" name="productId">
              <a-select v-model:value="form.productId" placeholder="选择关联应用" style="width: 100%">
                <a-select-option v-for="app in myApps" :key="app.productId" :value="app.productId">
                  {{ app.siteName || app.productName }}
                </a-select-option>
              </a-select>
            </a-form-item>
          </a-col>
          <a-col :span="6">
            <a-form-item label="问题分类" name="category">
              <a-select v-model:value="form.category" style="width: 100%">
                <a-select-option v-for="c in categoryOptions" :key="c.value" :value="c.value">
                  {{ c.label }}
                </a-select-option>
              </a-select>
            </a-form-item>
          </a-col>
          <a-col :span="6">
            <a-form-item label="优先级" name="priority">
              <a-select v-model:value="form.priority" style="width: 100%">
                <a-select-option v-for="p in priorityOptions" :key="p.value" :value="p.value">
                  {{ p.label }}
                </a-select-option>
              </a-select>
            </a-form-item>
          </a-col>
        </a-row>
        <a-form-item label="问题描述" name="content">
          <a-textarea
            v-model:value="form.content"
            :rows="6"
            placeholder="详细描述您遇到的问题，包括操作步骤、期望效果、实际效果等"
            show-count
            :maxlength="2000"
          />
        </a-form-item>
        <a-form-item label="附件（可选）">
          <a-upload
            v-model:file-list="createFileList"
            :custom-request="handleCreateUpload"
            :before-upload="beforeUpload"
            :on-remove="handleCreateRemove"
            multiple
            :max-count="5"
            list-type="text"
          >
            <a-button>
              <template #icon><PlusOutlined /></template>
              上传附件（最多5个，单个≤10MB）
            </a-button>
          </a-upload>
        </a-form-item>
        <div class="form-tip">
          <InfoCircleOutlined />
          提交后将自动分配给对应应用的技术负责人，通常在 1-2 个工作日内响应
        </div>
      </a-form>
    </a-modal>

    <!-- ========= 工单详情抽屉 ========= -->
    <a-drawer
      v-model:open="showDetail"
      :title="`工单详情 — ${currentTicket?.ticketNo}`"
      width="600"
      placement="right"
    >
      <template v-if="currentTicket">
        <!-- 状态栏 -->
        <div class="detail-status-bar">
          <a-space size="small" wrap>
            <a-tag :color="statusColor(currentTicket.status)" class="text-sm">
              {{ statusLabel(currentTicket.status) }}
            </a-tag>
            <a-tag :color="priorityColor(currentTicket.priority)">
              {{ priorityLabel(currentTicket.priority) }}
            </a-tag>
            <a-tag>{{ categoryLabel(currentTicket.category) }}</a-tag>
          </a-space>
          <div class="detail-actions">
            <a-button
              v-if="['pending','assigned','processing'].includes(currentTicket.status)"
              size="small"
              danger
              @click="handleClose"
            >
              关闭工单
            </a-button>
          </div>
        </div>

        <!-- 工单信息 -->
        <a-descriptions :column="2" size="small" class="detail-desc">
          <a-descriptions-item label="工单编号">{{ currentTicket.ticketNo }}</a-descriptions-item>
          <a-descriptions-item label="关联应用">
            📦 {{ currentTicket.productName || `#${currentTicket.productId}` }}
          </a-descriptions-item>
          <a-descriptions-item label="提交时间">{{ formatTime(currentTicket.createTime) }}</a-descriptions-item>
          <a-descriptions-item label="处理人">
            <span v-if="currentTicket.assigneeName">{{ currentTicket.assigneeName }}</span>
            <a-tag v-else color="orange">待分配</a-tag>
          </a-descriptions-item>
        </a-descriptions>

        <a-divider />

        <!-- 工单正文 -->
        <div class="detail-section-title">问题描述</div>
        <div class="detail-content">{{ currentTicket.content }}</div>
        <div v-if="currentTicket.attachments?.length" class="detail-attachments">
          <span class="detail-attachments-label">📎 附件：</span>
          <a
            v-for="url in currentTicket.attachments"
            :key="url"
            :href="getAttachmentUrl(url)"
            target="_blank"
            class="detail-attachment-link"
          >
            {{ url.split('/').slice(-1)[0] }}
          </a>
        </div>

        <a-divider />

        <!-- 回复列表 -->
        <div class="detail-section-title">
          沟通记录
          <a-spin v-if="repliesLoading" size="small" style="margin-left: 8px" />
        </div>

        <div class="reply-list">
          <div
            v-for="reply in replies"
            :key="reply.replyId"
            class="reply-item"
            :class="{ 'reply-staff': reply.isStaff }"
          >
            <a-avatar :size="32" :src="reply.userAvatar">
              <template #icon><UserOutlined /></template>
            </a-avatar>
            <div class="reply-body">
              <div class="reply-header">
                <span class="reply-name">{{ reply.userName }}</span>
                <a-tag v-if="reply.isStaff" color="blue" class="staff-tag">技术人员</a-tag>
                <span class="reply-time">{{ formatTime(reply.createTime) }}</span>
              </div>
              <div class="reply-content">{{ reply.content }}</div>
              <div v-if="reply.attachments?.length" class="reply-attachments">
                <a
                  v-for="url in reply.attachments"
                  :key="url"
                  :href="getAttachmentUrl(url)"
                  target="_blank"
                  class="reply-attachment-link"
                >
                  📎 {{ url.split('/').slice(-1)[0] }}
                </a>
              </div>
            </div>
          </div>
          <a-empty v-if="!repliesLoading && replies.length === 0" description="暂无回复" class="mt-4" />
        </div>

        <!-- 回复输入框 -->
        <div
          v-if="currentTicket && !['resolved','closed'].includes(currentTicket.status)"
          class="reply-input-wrap"
        >
          <a-textarea
            v-model:value="replyContent"
            placeholder="输入您的回复..."
            :rows="3"
            :maxlength="1000"
            show-count
          />
          <a-upload
            v-model:file-list="replyFileList"
            :custom-request="handleReplyUpload"
            :before-upload="beforeUpload"
            :on-remove="handleReplyRemove"
            multiple
            :max-count="5"
            list-type="text"
            class="mt-2"
          >
            <a-button size="small">
              <template #icon><PlusOutlined /></template>
              添加附件
            </a-button>
          </a-upload>
          <a-button
            type="primary"
            class="mt-2"
            :loading="replyLoading"
            :disabled="!replyContent.trim()"
            @click="handleReply"
          >
            发送回复
          </a-button>
        </div>
      </template>
    </a-drawer>
  </div>
</template>

<script setup lang="ts">
import { message } from 'ant-design-vue'
import {
  InfoCircleOutlined,
  MessageOutlined,
  PlusOutlined,
  UserOutlined,
} from '@ant-design/icons-vue'
import {
  getMyTickets,
  submitTicket,
  closeTicket,
  getTicketReplies,
  replyTicket,
  getTicketStats,
} from '@/api/ticket'
import type { Ticket, TicketReply, TicketSubmitForm } from '@/api/ticket/model'
import { pageAppProductAll } from '@/api/app/appProduct'
import { uploadFile } from '@/api/system/file'
import type { UploadFile } from 'ant-design-vue'

definePageMeta({ layout: 'console' })
useHead({ title: '工单管理 - 控制台' })

// ─── 状态 & 筛选 ───────────────────────────────────────────────
const loading = ref(false)
const error = ref('')
const tickets = ref<Ticket[]>([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = 15
const keywords = ref('')
const statusFilter = ref('all')
const appFilter = ref<number | undefined>(undefined)
const categoryFilter = ref<string | undefined>(undefined)

// ─── 应用列表（从 localStorage / API 获取） ───────────────────
const myApps = ref<{ productId: number; siteName?: string; productName?: string }[]>([])

// ─── 统计 ─────────────────────────────────────────────────────
const stats = ref({ total: 0, pending: 0, processing: 0, resolved: 0, closed: 0 })
const statsCards = computed(() => [
  { label: '全部工单', value: stats.value.total, colorClass: 'stat-default' },
  { label: '待处理', value: stats.value.pending, colorClass: 'stat-warning' },
  { label: '处理中', value: stats.value.processing, colorClass: 'stat-processing' },
  { label: '已解决', value: stats.value.resolved, colorClass: 'stat-success' },
])

// ─── 字典 ─────────────────────────────────────────────────────
const statusOptions = [
  { label: '全部', value: 'all' },
  { label: '待处理', value: 'pending' },
  { label: '已分配', value: 'assigned' },
  { label: '处理中', value: 'processing' },
  { label: '已解决', value: 'resolved' },
  { label: '已关闭', value: 'closed' },
]
const categoryOptions = [
  { label: '🐛 Bug 反馈', value: 'bug' },
  { label: '✨ 功能需求', value: 'feature' },
  { label: '❓ 咨询', value: 'consultation' },
  { label: '📢 投诉', value: 'complaint' },
  { label: '📋 其他', value: 'other' },
]
const priorityOptions = [
  { label: '⬇️ 低', value: 'low' },
  { label: '➡️ 普通', value: 'normal' },
  { label: '⬆️ 高', value: 'high' },
  { label: '🔥 紧急', value: 'urgent' },
]

function statusColor(s: string) {
  return { pending: 'orange', assigned: 'blue', processing: 'geekblue', resolved: 'green', closed: 'default', rejected: 'red' }[s] || 'default'
}
function statusLabel(s: string) {
  return { pending: '待处理', assigned: '已分配', processing: '处理中', resolved: '已解决', closed: '已关闭', rejected: '已拒绝' }[s] || s
}
function priorityColor(p: string) {
  return { low: 'default', normal: 'blue', high: 'orange', urgent: 'red' }[p] || 'default'
}
function priorityLabel(p: string) {
  return { low: '低', normal: '普通', high: '高', urgent: '紧急' }[p] || p
}
function categoryLabel(c: string) {
  return { bug: '🐛 Bug', feature: '✨ 需求', consultation: '❓ 咨询', complaint: '📢 投诉', other: '📋 其他' }[c] || c
}
function formatTime(t: string) {
  if (!t) return ''
  const d = new Date(t)
  const now = new Date()
  const diff = now.getTime() - d.getTime()
  if (diff < 60000) return '刚刚'
  if (diff < 3600000) return `${Math.floor(diff / 60000)} 分钟前`
  if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小时前`
  if (diff < 604800000) return `${Math.floor(diff / 86400000)} 天前`
  return d.toLocaleDateString('zh-CN')
}

// ─── 加载 ─────────────────────────────────────────────────────
async function loadTickets() {
  loading.value = true
  error.value = ''
  try {
    const res = await getMyTickets({
      keywords: keywords.value || undefined,
      status: statusFilter.value === 'all' ? undefined : (statusFilter.value as any),
      productId: appFilter.value,
      category: categoryFilter.value as any,
      page: currentPage.value,
      limit: pageSize,
    })
    tickets.value = (res?.data as any)?.data?.list || []
    total.value = (res?.data as any)?.data?.count || 0
  } catch (e: any) {
    error.value = e?.message || '加载失败'
  } finally {
    loading.value = false
  }
}

async function loadStats() {
  try {
    const res = await getTicketStats()
    Object.assign(stats.value, (res?.data as any)?.data || {})
  } catch {}
}

async function loadMyApps() {
  try {
    // 取当前登录用户 ID
    const userId = Number(localStorage.getItem('UserId') || 0)
    if (!userId) return
    const res = await pageAppProductAll({
      current: 1,
      size: 200,
      userId: userId,
    })
    myApps.value = res?.list ?? []
  } catch {}
}

function doSearch() {
  currentPage.value = 1
  loadTickets()
}

// ─── 提交工单 ─────────────────────────────────────────────────
const showCreate = ref(false)
const submitLoading = ref(false)
const formRef = ref()
const form = reactive<TicketSubmitForm>({
  title: '',
  content: '',
  productId: undefined as any,
  category: 'bug',
  priority: 'normal',
  attachments: [],
})
const rules = {
  title: [{ required: true, message: '请输入工单标题' }],
  productId: [{ required: true, message: '请选择关联应用' }],
  content: [{ required: true, message: '请描述您的问题' }, { min: 10, message: '描述不少于10字' }],
}

// 附件上传
type UploadRequestOption = { file?: File; onSuccess?: (body: unknown, file: File) => void; onError?: (err: unknown) => void }

/** 只有图片类型才附加 OSS 图片处理参数 */
function getAttachmentUrl(url: string) {
  const imageExts = /\.(jpg|jpeg|png|gif|webp|bmp|svg)(\?.*)?$/i
  if (imageExts.test(url)) {
    const base = url.split('?')[0]
    return `${base}?x-oss-process=image/resize,w_750/quality,Q_90`
  }
  return url
}
const createFileList = ref<UploadFile[]>([])
const replyFileList = ref<UploadFile[]>([])

function beforeUpload(file: File) {
  const maxMb = 10
  if (file.size > maxMb * 1024 * 1024) {
    message.error(`文件大小不能超过 ${maxMb}MB`)
    return false
  }
  return true
}

async function handleCreateUpload(option: UploadRequestOption) {
  const rawFile = option.file
  if (!rawFile) return
  try {
    const record = await uploadFile(rawFile)
    const url = (record?.url || record?.downloadUrl || '').trim()
    if (!url) throw new Error('上传成功但未返回文件地址')
    form.attachments = [...(form.attachments || []), url]
    createFileList.value = [
      ...createFileList.value.filter(f => f.status !== 'uploading'),
      { uid: url, name: rawFile.name, status: 'done', url } as UploadFile,
    ]
    option.onSuccess?.(record, rawFile)
  } catch (e) {
    option.onError?.(e)
    message.error(e instanceof Error ? e.message : '上传失败')
  }
}

function handleCreateRemove(file: UploadFile) {
  form.attachments = (form.attachments || []).filter(u => u !== file.url)
  createFileList.value = createFileList.value.filter(f => f.uid !== file.uid)
}

async function handleReplyUpload(option: UploadRequestOption) {
  const rawFile = option.file
  if (!rawFile) return
  try {
    const record = await uploadFile(rawFile)
    const url = (record?.url || record?.downloadUrl || '').trim()
    if (!url) throw new Error('上传成功但未返回文件地址')
    replyAttachments.value = [...replyAttachments.value, url]
    replyFileList.value = [
      ...replyFileList.value.filter(f => f.status !== 'uploading'),
      { uid: url, name: rawFile.name, status: 'done', url } as UploadFile,
    ]
    option.onSuccess?.(record, rawFile)
  } catch (e) {
    option.onError?.(e)
    message.error(e instanceof Error ? e.message : '上传失败')
  }
}

function handleReplyRemove(file: UploadFile) {
  replyAttachments.value = replyAttachments.value.filter(u => u !== file.url)
  replyFileList.value = replyFileList.value.filter(f => f.uid !== file.uid)
}

function openCreate() {
  showCreate.value = true
  createFileList.value = []
  form.attachments = []
}

async function handleSubmit() {
  try {
    await formRef.value?.validate()
  } catch {
    return
  }
  submitLoading.value = true
  try {
    // 将附件数组转为 JSON 字符串提交
    const submitData = {
      ...form,
      attachments: form.attachments?.length ? JSON.stringify(form.attachments) : null,
    }
    await submitTicket(submitData)
    message.success('工单已提交，将自动分配给技术人员')
    showCreate.value = false
    Object.assign(form, { title: '', content: '', productId: undefined, category: 'bug', priority: 'normal', attachments: [] })
    createFileList.value = []
    await loadTickets()
    await loadStats()
  } catch (e: any) {
    message.error(e?.message || '提交失败')
  } finally {
    submitLoading.value = false
  }
}

// ─── 工单详情 ─────────────────────────────────────────────────
const showDetail = ref(false)
const currentTicket = ref<Ticket | null>(null)
const replies = ref<TicketReply[]>([])
const repliesLoading = ref(false)
const replyContent = ref('')
const replyLoading = ref(false)
const replyAttachments = ref<string[]>([])

async function openDetail(ticket: Ticket) {
  currentTicket.value = ticket
  showDetail.value = true
  repliesLoading.value = true
  replyContent.value = ''
  replyAttachments.value = []
  replyFileList.value = []
  try {
    const res = await getTicketReplies(ticket.ticketId)
    replies.value = (res?.data as any)?.data || res?.data || []
  } catch {} finally {
    repliesLoading.value = false
  }
}

async function handleReply() {
  if (!replyContent.value.trim()) return
  replyLoading.value = true
  try {
    await replyTicket({
      ticketId: currentTicket.value!.ticketId,
      content: replyContent.value.trim(),
      attachments: replyAttachments.value.length ? JSON.stringify(replyAttachments.value) : undefined,
    })
    message.success('回复成功')
    replyContent.value = ''
    replyAttachments.value = []
    replyFileList.value = []
    const res = await getTicketReplies(currentTicket.value!.ticketId)
    replies.value = (res?.data as any)?.data || res?.data || []
    // 更新列表中的回复数
    const t = tickets.value.find(t => t.ticketId === currentTicket.value!.ticketId)
    if (t) t.replyCount++
  } catch (e: any) {
    message.error(e?.message || '回复失败')
  } finally {
    replyLoading.value = false
  }
}

async function handleClose() {
  try {
    await closeTicket(currentTicket.value!.ticketId)
    message.success('工单已关闭')
    showDetail.value = false
    await loadTickets()
    await loadStats()
  } catch (e: any) {
    message.error(e?.message || '操作失败')
  }
}

// ─── 初始化 ───────────────────────────────────────────────────
onMounted(() => {
  loadMyApps()
  loadTickets()
  loadStats()
})
</script>

<style scoped>
/* ─── 统计卡片 ─────────────────────────────────────── */
.stat-card {
  border-radius: 10px;
  padding: 16px 20px;
  text-align: center;
}
.stat-value {
  font-size: 28px;
  font-weight: 700;
  line-height: 1;
}
.stat-label {
  font-size: 12px;
  margin-top: 6px;
  opacity: 0.7;
}
.stat-default { background: #f5f5f5; color: #333; }
.stat-warning { background: #fff7e6; color: #d46b08; }
.stat-processing { background: #e6f4ff; color: #0958d9; }
.stat-success { background: #f6ffed; color: #389e0d; }

/* ─── 筛选栏 ─────────────────────────────────────── */
.filter-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 16px;
}
.filter-right { flex-shrink: 0; }

/* ─── 工单列表 ─────────────────────────────────────── */
.ticket-list { display: flex; flex-direction: column; gap: 10px; }

.ticket-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
  border-radius: 10px;
  border: 1px solid #f0f0f0;
  background: #fff;
  cursor: pointer;
  transition: all 0.2s;
}
.ticket-item:hover { border-color: #4f46e5; box-shadow: 0 2px 8px rgba(79,70,229,0.08); }
.ticket-item.has-unread { border-left: 3px solid #4f46e5; }

.ticket-item-left { flex: 1; min-width: 0; }
.ticket-badge-row { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; flex-wrap: wrap; }
.status-tag, .priority-tag, .category-tag { font-size: 11px !important; padding: 0 6px !important; }
.unread-dot {
  width: 7px; height: 7px;
  background: #4f46e5; border-radius: 50%; display: inline-block;
}
.ticket-title {
  font-size: 14px; font-weight: 500; color: #1a1a2e;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  margin-bottom: 4px;
}
.ticket-meta { display: flex; gap: 12px; font-size: 12px; color: #8c8c8c; flex-wrap: wrap; }
.meta-no { font-family: monospace; }

.ticket-item-right {
  display: flex; flex-direction: column;
  align-items: flex-end; gap: 8px; flex-shrink: 0; margin-left: 16px;
}
.assignee { display: flex; align-items: center; gap: 6px; }
.assignee-name { font-size: 12px; color: #595959; }
.assignee-pending { font-size: 12px; color: #faad14; }
.reply-count { display: flex; align-items: center; gap: 4px; font-size: 12px; color: #8c8c8c; }

/* ─── 分页 ─────────────────────────────────────────── */
.pagination-wrap { display: flex; justify-content: flex-end; margin-top: 20px; }
.empty-wrap { padding: 40px 0; }

/* ─── 表单提示 ─────────────────────────────────────── */
.form-tip {
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: #8c8c8c;
  background: #f5f5f5; padding: 8px 12px; border-radius: 6px;
}

/* ─── 详情抽屉 ─────────────────────────────────────── */
.detail-status-bar {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 16px; flex-wrap: wrap; gap: 8px;
}
.detail-desc { margin-bottom: 0; }
.detail-section-title {
  font-size: 13px; font-weight: 600; color: #595959;
  margin-bottom: 10px; display: flex; align-items: center;
}
.detail-content {
  font-size: 14px; color: #262626; line-height: 1.7;
  white-space: pre-wrap; background: #fafafa;
  padding: 12px 14px; border-radius: 8px;
}
.detail-attachments {
  margin-top: 8px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
}
.detail-attachments-label { font-size: 12px; color: #8c8c8c; }
.detail-attachment-link {
  font-size: 12px; color: #1677ff; text-decoration: none;
  background: #f0f5ff; padding: 2px 8px; border-radius: 4px; border: 1px solid #d6e4ff;
}
.detail-attachment-link:hover { text-decoration: underline; background: #e6f0ff; }

/* ─── 回复 ─────────────────────────────────────────── */
.reply-list { display: flex; flex-direction: column; gap: 14px; max-height: 320px; overflow-y: auto; }
.reply-item { display: flex; gap: 10px; align-items: flex-start; }
.reply-staff .reply-body { background: #e6f4ff; }
.reply-body {
  flex: 1; background: #f5f5f5; border-radius: 8px; padding: 10px 12px;
}
.reply-header { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.reply-name { font-weight: 500; font-size: 13px; }
.staff-tag { font-size: 11px !important; padding: 0 5px !important; }
.reply-time { font-size: 11px; color: #8c8c8c; margin-left: auto; }
.reply-content { font-size: 13px; color: #262626; line-height: 1.6; white-space: pre-wrap; }
.reply-attachments { margin-top: 6px; display: flex; flex-wrap: wrap; gap: 6px; }
.reply-attachment-link { font-size: 12px; color: #1677ff; text-decoration: none; }
.reply-attachment-link:hover { text-decoration: underline; }
.reply-input-wrap { margin-top: 16px; }

/* ─── card ─────────────────────────────────────────── */
.card { border-radius: 12px; }
</style>
