<template>
  <div class="delivery-project-page">
    <!-- 查询表单 -->
    <div class="query-form-container">
      <div class="query-form-basic">
        <el-form class="query-form-fields" :inline="true" :model="queryForm" size="small">
          <el-form-item label="事件标题">
            <el-input v-model="queryForm.deliveryEventTitle" clearable placeholder="请输入" style="width: 180px" />
          </el-form-item>
          <el-form-item label="事件状态">
            <el-select
              v-model="queryForm.deliveryEventStatus"
              collapse-tags
              multiple
              placeholder="请选择"
              style="width: 140px">
              <el-option
                v-for="dict in deliveryEventStatusOptions"
                :key="dict.key"
                :label="dict.value"
                :value="dict.key" />
            </el-select>
          </el-form-item>
          <el-form-item label="反馈人">
            <el-input v-model="queryForm.feedbackReporter" clearable placeholder="请输入" style="width: 120px" />
          </el-form-item>
          <el-form-item label="负责人">
            <el-select
              v-model="queryForm.opsUserName"
              class="query-select--owner"
              clearable
              collapse-tags
              filterable
              :loading="opsUsersLoading"
              multiple
              placeholder="请选择"
              remote
              :remote-method="remoteFetchOpsUsers"
              reserve-keyword
              @visible-change="handleOpsUserVisibleChange">
              <el-option v-for="u in opsUserOptions" :key="u.value" :label="u.label" :value="u.label" />
            </el-select>
          </el-form-item>
          <el-form-item label="反馈时间">
            <el-date-picker
              v-model="queryForm.feedbackDateRange"
              end-placeholder="结束日期"
              range-separator="至"
              start-placeholder="开始日期"
              style="width: 300px"
              type="daterange"
              value-format="yyyy-MM-dd" />
          </el-form-item>
        </el-form>
        <div class="query-form-actions">
          <el-button icon="el-icon-search" type="primary" @click="handleSearch">查询</el-button>
          <el-button icon="el-icon-refresh-right" @click="handleReset">重置</el-button>
          <el-button type="text" @click="showAdvanced = !showAdvanced">
            {{ showAdvanced ? '收起' : '高级筛选' }}
            <i :class="showAdvanced ? 'el-icon-arrow-up' : 'el-icon-arrow-down'" />
          </el-button>
          <el-divider direction="vertical" />
          <el-button icon="el-icon-plus" type="success" @click="handleAdd">新增</el-button>
        </div>
      </div>

      <!-- 高级查询条件 -->
      <div v-show="showAdvanced" class="query-form-advanced">
        <el-form :inline="true" :model="queryForm" size="small">
          <el-form-item label="事件类型">
            <el-select
              v-model="queryForm.deliveryEventType"
              collapse-tags
              multiple
              placeholder="请选择"
              style="width: 160px">
              <el-option
                v-for="dict in deliveryEventTypeOptions"
                :key="dict.key"
                :label="dict.value"
                :value="dict.key" />
            </el-select>
          </el-form-item>
          <el-form-item label="事件结果">
            <el-select
              v-model="queryForm.deliveryEventResult"
              collapse-tags
              multiple
              placeholder="请选择"
              style="width: 140px">
              <el-option
                v-for="dict in deliveryEventResultOptions"
                :key="dict.key"
                :label="dict.value"
                :value="dict.key" />
            </el-select>
          </el-form-item>
          <el-form-item label="事件描述">
            <el-input v-model="queryForm.deliveryEventDesc" clearable placeholder="请输入" style="width: 180px" />
          </el-form-item>
          <el-form-item label="处理时间">
            <el-date-picker
              v-model="queryForm.completeDateRange"
              end-placeholder="结束日期"
              range-separator="至"
              start-placeholder="开始日期"
              style="width: 300px"
              type="daterange"
              value-format="yyyy-MM-dd" />
          </el-form-item>
        </el-form>
      </div>
    </div>

    <!-- 主体内容 -->
    <div class="main-content">
      <!-- 左侧项目筛选 -->
      <div :class="['project-sidebar', { collapsed: sidebarCollapsed }]">
        <div class="sidebar-header">
          <div v-if="!sidebarCollapsed" class="sidebar-title-group">
            <span class="sidebar-title">项目列表</span>
            <button
              :class="['sidebar-action-btn', { active: showSidebarFilters }]"
              :title="showSidebarFilters ? '隐藏项目筛选' : '显示项目筛选'"
              type="button"
              @click="toggleSidebarFilters">
              <i class="el-icon-search" />
            </button>
          </div>
          <div class="sidebar-actions">
            <button
              class="collapse-trigger"
              :title="sidebarCollapsed ? '展开项目列表' : '折叠项目列表'"
              type="button"
              @click="toggleSidebar">
              <i :class="sidebarCollapsed ? 'el-icon-d-arrow-right' : 'el-icon-d-arrow-left'" />
            </button>
          </div>
        </div>
        <div v-if="sidebarCollapsed" class="sidebar-collapsed-label">项目列表</div>
        <template v-if="!sidebarCollapsed">
          <template v-if="showSidebarFilters">
            <div class="project-status-filter">
              <el-radio-group v-model="projectStatusFilter" size="small" @change="handleProjectStatusChange">
                <el-radio-button label="">全部</el-radio-button>
                <el-radio-button label="pending">待分配</el-radio-button>
                <el-radio-button label="delivering">交付中</el-radio-button>
                <el-radio-button label="delivered">已验收</el-radio-button>
              </el-radio-group>
            </div>
            <el-input
              v-model="projectSearch"
              class="project-search"
              clearable
              placeholder="搜索项目/销售/交付"
              prefix-icon="el-icon-search"
              size="small"
              @input="onProjectSearchDebounced" />
          </template>
          <div ref="projectList" class="project-list">
            <div
              v-if="allProjectOption"
              :class="['project-item', 'project-item--all', { active: selectedProject === allProjectOption.id }]"
              @click="selectProject(allProjectOption.id)">
              <div>
                <div class="project-overview-label">{{ allProjectOption.name }}</div>
                <div class="project-overview-desc">查看全部项目交付进度</div>
              </div>
            </div>
            <div
              v-for="project in projectCards"
              :key="project.id"
              :class="['project-card', { active: selectedProject === project.id }]"
              @click="selectProject(project.id, project)">
              <div class="project-card-top">
                <div class="project-card-tags">
                  <span class="project-contract clickable" @click.stop="handleViewProjectInfo(project)">
                    {{ project.contractNo || '-' }}
                  </span>
                  <span class="project-line-tag">{{ getProductLineLabel(project.productLine) }}</span>
                </div>
                <span :class="['project-status-tag', 'project-status-tag--' + project.status]">
                  {{ selectDictLabel(projectStatusOptions, project.status) }}
                </span>
              </div>
              <div class="project-card-title" :title="project.name">{{ project.name }}</div>
              <div class="project-card-meta">
                <div class="project-card-meta-item">
                  <i class="el-icon-user project-card-meta-icon" title="销售负责人" />
                  <span class="project-card-meta-value" :title="project.salesOwner || '-'">
                    {{ project.salesOwner || '-' }}
                  </span>
                </div>
                <div class="project-card-meta-item">
                  <i
                    class="el-icon-s-custom project-card-meta-icon project-card-meta-icon--delivery"
                    title="交付负责人" />
                  <span class="project-card-meta-value" :title="project.deliveryOwner || '-'">
                    {{ project.deliveryOwner || '-' }}
                  </span>
                  <i
                    v-if="project.deliveryOwner && canManageDelivery"
                    class="el-icon-refresh-right reassign-btn"
                    title="改派"
                    @click.stop="handleReassign(project)" />
                </div>
              </div>
              <i v-if="selectedProject === project.id" class="el-icon-check project-card-check" />
            </div>
            <div v-if="projectLoading" class="project-list-loading">
              <i class="el-icon-loading" />
              加载中...
            </div>
            <div v-else-if="!projectHasMore && projects.length > 1" class="project-list-end">没有更多了</div>
          </div>
        </template>
      </div>

      <!-- 右侧数据表格 -->
      <div class="table-container">
        <div class="table-scroll-wrapper">
          <el-table
            v-loading="loading"
            border
            class="delivery-project-table"
            :data="tableData"
            :fit="false"
            height="100%"
            stripe
            style="width: 100%"
            @row-dblclick="handleRowClick">
            <el-table-column align="center" type="index" width="50" />
            <el-table-column label="事件标题" min-width="320" show-overflow-tooltip>
              <template slot-scope="{ row }">
                <span class="event-title-text">
                  {{ row.deliveryEventTitle || row.delivery_event_title || '-' }}
                </span>
              </template>
            </el-table-column>
            <el-table-column label="项目名称" min-width="200" show-overflow-tooltip>
              <template slot-scope="{ row }">
                {{ row.projectName || row.project_name || '-' }}
              </template>
            </el-table-column>
            <el-table-column
              label="事件类型"
              :render-header="renderSortableHeader('事件类型', 'deliveryEventType')"
              width="140">
              <template slot-scope="{ row }">
                <el-tag size="small" type="info">
                  {{ selectDictLabel(deliveryEventTypeOptions, row.deliveryEventType || row.delivery_event_type) }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column
              label="事件状态"
              :render-header="renderSortableHeader('事件状态', 'deliveryEventStatus')"
              width="120">
              <template slot-scope="{ row }">
                <el-tag
                  size="small"
                  :type="getDeliveryEventStatusTagType(row.deliveryEventStatus || row.delivery_event_status)">
                  {{
                    selectDictLabel(deliveryEventStatusOptions, row.deliveryEventStatus || row.delivery_event_status)
                  }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column
              label="事件结果"
              :render-header="renderSortableHeader('事件结果', 'deliveryEventResult')"
              width="120">
              <template slot-scope="{ row }">
                {{ selectDictLabel(deliveryEventResultOptions, row.deliveryEventResult || row.delivery_event_result) }}
              </template>
            </el-table-column>
            <el-table-column
              label="负责人"
              :render-header="renderSortableHeader('负责人', 'opsUserName')"
              show-overflow-tooltip
              width="130">
              <template slot-scope="{ row }">
                {{ row.opsUserName || row.ops_user_name || '-' }}
              </template>
            </el-table-column>
            <el-table-column
              label="处理时间"
              :render-header="renderSortableHeader('处理时间', 'completeTime')"
              width="180">
              <template slot-scope="{ row }">
                {{ formatEventTime(row.completeTime || row.complete_time) }}
              </template>
            </el-table-column>
            <el-table-column label="处理说明" min-width="240" show-overflow-tooltip>
              <template slot-scope="{ row }">
                {{ row.completeDesc || row.complete_desc || '-' }}
              </template>
            </el-table-column>
            <el-table-column label="是否现场" width="90">
              <template slot-scope="{ row }">
                {{ selectDictLabel(onSiteOptions, row.onSite || row.on_site) }}
              </template>
            </el-table-column>
            <el-table-column
              label="反馈人"
              :render-header="renderSortableHeader('反馈人', 'feedbackReporter')"
              show-overflow-tooltip
              width="130">
              <template slot-scope="{ row }">
                {{ row.feedbackReporter || row.feedback_reporter || '-' }}
              </template>
            </el-table-column>
            <el-table-column
              label="反馈时间"
              :render-header="renderSortableHeader('反馈时间', 'feedbackDate')"
              width="180">
              <template slot-scope="{ row }">
                {{ formatEventTime(row.feedbackDate || row.feedback_date) }}
              </template>
            </el-table-column>
            <el-table-column label="反馈来源" width="100">
              <template slot-scope="{ row }">
                {{ selectDictLabel(feedbackSourceOptions, row.feedbackSource || row.feedback_source) }}
              </template>
            </el-table-column>
            <el-table-column fixed="right" header-align="center" label="操作" width="200">
              <template slot-scope="{ row }">
                <el-button
                  v-if="String(row.deliveryEventStatus || row.delivery_event_status) !== '30'"
                  size="mini"
                  type="text"
                  @click.stop="handleEdit(row)">
                  编辑
                </el-button>
                <el-button
                  v-if="['10', '20'].includes(String(row.deliveryEventStatus || row.delivery_event_status))"
                  size="mini"
                  type="text"
                  @click.stop="handleProcess(row)">
                  处理
                </el-button>
                <el-button v-if="showProgressButton(row)" size="mini" type="text" @click.stop="handleProgress(row)">
                  进展
                </el-button>
                <el-button
                  v-if="['10', '20'].includes(String(row.deliveryEventStatus || row.delivery_event_status))"
                  size="mini"
                  style="color: #f56c6c"
                  type="text"
                  @click.stop="handleCancelEvent(row)">
                  作废
                </el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>

        <!-- 分页 -->
        <div class="pagination-wrapper">
          <el-pagination
            background
            :current-page.sync="queryForm.pageNum"
            layout="total, sizes, prev, pager, next, jumper"
            :page-size.sync="queryForm.pageSize"
            :page-sizes="[10, 20, 50, 100]"
            :total="total"
            @current-change="handleCurrentChange"
            @size-change="handleSizeChange" />
        </div>
      </div>
    </div>

    <!-- 编辑弹窗 -->
    <delivery-project-event-edit
      :data="editData"
      :project-id="selectedProject"
      :visible.sync="editDialogVisible"
      @refresh="handleRefreshEvent" />

    <!-- 详情弹窗 -->
    <delivery-project-event-detail
      :data="currentRow"
      :process-mode="detailMode === 'process'"
      :read-only="detailMode === 'view'"
      :show-close-event-dialog.sync="showCloseEventDialog"
      :visible.sync="detailVisible"
      @refresh="handleRefreshEvent" />

    <!-- 作废事件弹窗 -->
    <el-dialog :close-on-click-modal="false" title="作废事件" :visible.sync="cancelEventDialogVisible" width="500px">
      <el-form
        ref="cancelEventForm"
        label-width="100px"
        :model="cancelEventForm"
        :rules="cancelEventRules"
        size="small">
        <el-form-item label="作废原因" prop="cancelReason">
          <el-input v-model="cancelEventForm.cancelReason" placeholder="请输入作废原因..." :rows="4" type="textarea" />
        </el-form-item>
        <el-form-item label="附件上传">
          <el-upload
            action=""
            :auto-upload="false"
            :file-list="cancelEventFileList"
            :limit="5"
            :multiple="true"
            :on-change="handleCancelEventFileChange"
            :on-remove="handleCancelEventFileRemove">
            <el-button size="small" type="primary">选择文件</el-button>
            <div slot="tip" class="el-upload__tip">最多上传5个文件，单个文件不超过20MB</div>
          </el-upload>
        </el-form-item>
      </el-form>
      <div slot="footer">
        <el-button size="small" @click="handleCancelEventDialogCancel">取消</el-button>
        <el-button :loading="submitLoading" size="small" type="primary" @click="handleSaveCancelEvent">确定</el-button>
      </div>
    </el-dialog>

    <!-- 项目负责人指派/改派弹窗 -->
    <delivery-project-assign
      :data="assignDialogData"
      :visible.sync="assignDialogVisible"
      @success="handleAssignSuccess" />

    <!-- 项目信息弹窗 -->
    <project-info-dialog :project-id="selectedProjectId" :visible.sync="projectInfoDialogVisible" />

    <!-- 研发任务进展弹窗 -->
    <delivery-project-event-progress :event="progressEventRow" :visible.sync="progressDialogVisible" />
  </div>
</template>

<script>
  import deliveryProjectApi from '@/api/devops/deliveryProject'
  import deliveryProjectEventApi from '@/api/devops/deliveryProjectEvent'
  import userApi from '@/api/system/user'
  import { mapGetters } from 'vuex'
  import { DEVOPS_DEV_DEPT_ID } from '@/config/devops.config'
  import DeliveryProjectEventEdit from './components/DeliveryProjectEventEdit'
  import DeliveryProjectEventDetail from './components/DeliveryProjectEventDetail'
  import DeliveryProjectEventProgress from './components/DeliveryProjectEventProgress'
  import DeliveryProjectAssign from './components/DeliveryProjectAssign'
  import ProjectInfoDialog from '../components/ProjectInfoDialog'
  import { parseTime } from '@/utils'
  import debounce from 'lodash/debounce'
  import { uploadFileToRichtextServer } from '@/utils/richtextUpload'
  import { sanitizeHtml } from '@/utils/safeHtml'
  import { deliveryEventStatusTagTypes, getTagType } from '@/config/devopsTagTypes'
  import dictApi from '@/api/system/dict'

  export default {
    name: 'DeliveryProject',
    components: {
      DeliveryProjectEventEdit,
      DeliveryProjectEventDetail,
      DeliveryProjectEventProgress,
      DeliveryProjectAssign,
      ProjectInfoDialog,
    },
    data() {
      return {
        queryForm: {
          pageNum: 1,
          pageSize: 20,
          projectId: '',
          deliveryEventTitle: '',
          deliveryEventDesc: '',
          deliveryEventType: [],
          deliveryEventStatus: [],
          deliveryEventResult: [],
          opsUserName: '',
          feedbackReporter: '',
          feedbackDateRange: [],
          completeDateRange: [],
          sortFields: [],
        },
        showAdvanced: false,
        opsUserOptions: [],
        opsUsersLoading: false,
        tableData: [],
        total: 0,
        loading: false,
        submitLoading: false,
        sidebarCollapsed: false,
        showSidebarFilters: true,
        projectSearch: '',
        projectPageNum: 1,
        projectPageSize: 20,
        projectTotal: 0,
        projectLoading: false,
        projectHasMore: true,
        projectStatusFilter: 'delivering',
        selectedProject: '',
        projects: [{ id: '', name: '全部' }],
        editDialogVisible: false,
        editData: null,
        productLineDict: [],
        projectStatusOptions: [],
        deliveryEventTypeOptions: [],
        deliveryEventStatusOptions: [],
        deliveryEventResultOptions: [],
        feedbackSourceOptions: [],
        onSiteOptions: [],
        detailVisible: false,
        detailMode: 'view',
        currentRow: null,
        showCloseEventDialog: false,
        // 作废事件弹窗相关数据
        cancelEventDialogVisible: false,
        cancelEventForm: {
          cancelReason: '',
        },
        cancelEventRules: {
          cancelReason: [{ required: true, message: '请输入作废原因', trigger: 'blur' }],
        },
        cancelEventFileList: [],
        cancelEventUploadFiles: [],
        // 项目负责人指派/改派弹窗相关数据
        assignDialogVisible: false,
        assignDialogData: null,
        // 项目信息弹窗
        projectInfoDialogVisible: false,
        selectedProjectId: '',
        // 进展弹窗相关数据
        progressDialogVisible: false,
        progressEventRow: null,
      }
    },
    computed: {
      ...mapGetters({
        roleKeys: 'user/roleKeys',
      }),
      canManageDelivery() {
        return (
          this.roleKeys.includes('ResearchAndDevelopmentDirector') ||
          this.roleKeys.includes('ResearchAndDevelopmentSupervisor')
        )
      },
      filteredProjects() {
        const statusFilter = this.projectStatusFilter

        return this.projects.filter((project) => {
          if (project.id === '') return true

          // 状态筛选
          if (statusFilter) {
            let statusList = []
            if (statusFilter === 'pending') {
              statusList = ['10']
            } else if (statusFilter === 'delivering') {
              statusList = ['20', '30', '40']
            } else if (statusFilter === 'delivered') {
              statusList = ['50']
            } else {
              statusList = statusFilter.split(',')
            }
            if (!statusList.includes(project.status)) {
              return false
            }
          } else {
            if (project.status === '90') {
              return false
            }
          }

          return true
        })
      },
      allProjectOption() {
        return this.filteredProjects.find((project) => project.id === '')
      },
      projectCards() {
        return this.filteredProjects.filter((project) => project.id !== '')
      },
    },
    created() {
      this.getOptions()
      this.fetchData()
      this.onProjectSearchDebounced = debounce(() => {
        this.projectPageNum = 1
        this.projects = [{ id: '', name: '全部' }]
        this.projectHasMore = true
        this.fetchProjectList()
      }, 300)
    },
    mounted() {
      this.$nextTick(() => {
        const el = this.$refs.projectList
        if (!el) return
        this._onProjectListScroll = debounce(() => {
          if (!this.projectHasMore || this.projectLoading) return
          const { scrollHeight, scrollTop, clientHeight } = el
          if (scrollHeight - scrollTop - clientHeight <= 80) {
            this.projectPageNum++
            this.fetchProjectList()
          }
        }, 200)
        el.addEventListener('scroll', this._onProjectListScroll)
      })
    },
    beforeDestroy() {
      if (this._onProjectListScroll) {
        this._onProjectListScroll.cancel()
        const el = this.$refs.projectList
        if (el) el.removeEventListener('scroll', this._onProjectListScroll)
      }
      if (this.onProjectSearchDebounced && this.onProjectSearchDebounced.cancel) {
        this.onProjectSearchDebounced.cancel()
      }
    },
    methods: {
      getTagType,
      getDeliveryEventStatusTagType(status) {
        return getTagType(deliveryEventStatusTagTypes, status, 'info')
      },
      parseTime,
      sanitizeHtml,

      getOptions() {
        dictApi
          .getDictDataByTypes([
            'sys_product_line',
            'delivery_project_status',
            'delivery_event_type',
            'delivery_event_status',
            'delivery_event_result',
            'feedback_source',
            'sys_yes_no',
          ])
          .then((res) => {
            const dicts = res.data || {}
            this.productLineDict = (dicts.sys_product_line && dicts.sys_product_line.values) || []
            this.projectStatusOptions = (dicts.delivery_project_status && dicts.delivery_project_status.values) || []
            this.deliveryEventTypeOptions = (dicts.delivery_event_type && dicts.delivery_event_type.values) || []
            this.deliveryEventStatusOptions = (dicts.delivery_event_status && dicts.delivery_event_status.values) || []
            this.deliveryEventResultOptions = (dicts.delivery_event_result && dicts.delivery_event_result.values) || []
            this.feedbackSourceOptions = (dicts.feedback_source && dicts.feedback_source.values) || []
            this.onSiteOptions = (dicts.sys_yes_no && dicts.sys_yes_no.values) || []
          })
          .catch((err) => console.log(err))
      },

      // 获取项目列表（支持滚动分页）
      async fetchProjectList() {
        if (this.projectLoading) return

        this.projectLoading = true
        try {
          const params = {
            pageNum: this.projectPageNum,
            pageSize: this.projectPageSize,
            productLine: '10,20,30,40,50,60',
            sortField: 'contract_no',
            sortOrder: 'desc',
            attribute9: '10',
          }
          // 状态筛选
          if (this.projectStatusFilter) {
            const statusMap = {
              pending: '10',
              delivering: '20,30,40',
              delivered: '50',
            }
            params.projectStatus = statusMap[this.projectStatusFilter] || this.projectStatusFilter
          } else {
            params.projectStatus = '10,20,30,40,50'
          }

          const keyword = this.projectSearch.trim()
          if (keyword) {
            params.keyWords = keyword
          }

          const res = await deliveryProjectApi.getDelegatedProjectList(params)
          if (res.code === 200 && res.data && res.data.list) {
            const projectList = (res.data?.list || []).map((item) => ({
              id: String(item.id),
              name: item.projectName || item.project_name,
              contractNo: item.contractNo || item.contract_no,
              productLine: item.productLine || item.product_line,
              salesOwner: item.salesUserName || item.sales_user_name,
              deliveryOwner: item.deliveryUserName || item.delivery_user_name,
              deliveryUserId: item.deliveryUserId || item.delivery_user_id,
              status: String(item.projectStatus || item.project_status),
            }))
            const total = res.data?.total || 0
            projectList.sort((a, b) => (b.contractNo || '').localeCompare(a.contractNo || ''))

            if (this.projectPageNum === 1) {
              this.projects = [{ id: '', name: '全部' }, ...projectList]
            } else {
              this.projects = this.projects.concat(projectList)
            }

            this.projectTotal = total
            this.projectHasMore = this.projects.length - 1 < total

            // 如果选中的项目不在当前列表中，清除选中
            if (this.selectedProject && !this.projects.some((project) => project.id === this.selectedProject)) {
              this.selectedProject = ''
              this.queryForm.projectId = ''
              this.queryForm.pageNum = 1
              this.fetchEventData()
            }
          }
        } catch (error) {
          console.error('获取项目列表失败:', error)
          if (this.projectPageNum === 1) {
            this.projects = [{ id: '', name: '全部' }]
          }
        } finally {
          this.projectLoading = false
          // 内容未填满容器时自动加载下一页
          this.$nextTick(() => {
            const el = this.$refs.projectList
            if (el && this.projectHasMore && !this.projectLoading) {
              if (el.scrollHeight <= el.clientHeight) {
                this.projectPageNum++
                this.fetchProjectList()
              }
            }
          })
        }
      },

      // 获取事件列表数据
      async fetchEventData() {
        this.loading = true
        try {
          const params = {
            pageNum: this.queryForm.pageNum,
            pageSize: this.queryForm.pageSize,
            projectId: this.queryForm.projectId ? parseInt(this.queryForm.projectId) : 0,
            deliveryEventTitle: this.queryForm.deliveryEventTitle,
            deliveryEventDesc: this.queryForm.deliveryEventDesc,
            deliveryEventType: this.queryForm.deliveryEventType,
            deliveryEventStatus: this.queryForm.deliveryEventStatus,
            deliveryEventResult: this.queryForm.deliveryEventResult,
            opsUserName: this.queryForm.opsUserName,
            feedbackReporter: this.queryForm.feedbackReporter,
            sortFields: this.queryForm.sortFields,
          }

          // 处理反馈时间范围
          if (this.queryForm.feedbackDateRange && this.queryForm.feedbackDateRange.length === 2) {
            params.feedbackDateStart = this.queryForm.feedbackDateRange[0] + ' 00:00:00'
            params.feedbackDateEnd = this.queryForm.feedbackDateRange[1] + ' 23:59:59'
          }

          // 处理处理时间范围
          if (this.queryForm.completeDateRange && this.queryForm.completeDateRange.length === 2) {
            params.completeTimeStart = this.queryForm.completeDateRange[0] + ' 00:00:00'
            params.completeTimeEnd = this.queryForm.completeDateRange[1] + ' 23:59:59'
          }

          const res = await deliveryProjectEventApi.getList(params)
          if (res.code === 200 && res.data) {
            this.tableData = res.data?.list || []
            this.total = res.data?.total || 0
          } else {
            this.tableData = []
            this.total = 0
          }
        } catch (error) {
          console.error('获取事件数据失败:', error)
          this.tableData = []
          this.total = 0
          this.$message.error('获取事件数据失败')
        } finally {
          this.loading = false
        }
      },

      // 获取列表数据（项目+事件）
      async fetchData() {
        await this.fetchProjectList()
        await this.fetchEventData()
      },

      // 搜索
      handleSearch() {
        this.queryForm.pageNum = 1
        this.fetchEventData()
      },

      // 重置
      handleReset() {
        this.queryForm = {
          pageNum: 1,
          pageSize: 20,
          projectId: this.queryForm.projectId,
          deliveryEventTitle: '',
          deliveryEventDesc: '',
          deliveryEventType: [],
          deliveryEventStatus: [],
          deliveryEventResult: [],
          opsUserName: [],
          feedbackReporter: '',
          feedbackDateRange: [],
          completeDateRange: [],
          sortFields: [],
        }
        this.fetchEventData()
      },

      // 新增
      handleAdd() {
        this.editData = null
        this.editDialogVisible = true
      },

      // 导出
      handleExport() {
        this.$message.info('导出功能开发中...')
      },

      // 编辑
      async handleEdit(row) {
        // 校验事件状态
        const status = row.deliveryEventStatus || row.delivery_event_status
        if (String(status) === '30') {
          this.$message.warning('该事件已关闭，不允许编辑')
          return
        }
        if (String(status) === '90') {
          this.$message.warning('该事件已作废，不允许编辑')
          return
        }

        try {
          const res = await deliveryProjectEventApi.getById(row.id)
          if (res.code === 200 && res.data) {
            const latestStatus = res.data.deliveryEventStatus || res.data.delivery_event_status
            if (String(latestStatus) === '30') {
              this.$message.warning('该事件已关闭，不允许编辑')
              this.fetchEventData()
              return
            }
            if (String(latestStatus) === '90') {
              this.$message.warning('该事件已作废，不允许编辑')
              this.fetchEventData()
              return
            }
            this.editData = res.data
          } else {
            this.editData = row
          }
        } catch (error) {
          console.error('获取事件详情失败:', error)
          this.editData = row
        } finally {
          this.editDialogVisible = true
        }
      },

      // 查看详情
      handleView(row) {
        this.currentRow = row
        this.detailMode = 'view'
        this.detailVisible = true
      },

      // 处理事件 - 打开详情弹窗（处理模式，可以编辑）
      async handleProcess(row) {
        // 校验事件状态
        const status = row.deliveryEventStatus || row.delivery_event_status
        if (String(status) === '30') {
          this.$message.warning('该事件已关闭，无法进行操作')
          return
        }
        if (String(status) === '90') {
          this.$message.warning('该事件已作废，无法进行操作')
          return
        }

        // 获取最新事件详情
        try {
          const res = await deliveryProjectEventApi.getById(row.id)
          if (res.code === 200 && res.data) {
            const latestStatus = res.data.deliveryEventStatus || res.data.delivery_event_status
            if (String(latestStatus) === '30') {
              this.$message.warning('该事件已关闭，无法进行操作')
              this.fetchEventData()
              return
            }
            if (String(latestStatus) === '90') {
              this.$message.warning('该事件已作废，无法进行操作')
              this.fetchEventData()
              return
            }
            this.currentRow = res.data
          } else {
            this.currentRow = row
          }
        } catch (error) {
          console.error('获取事件详情失败:', error)
          this.currentRow = row
        }

        this.detailMode = 'process'
        this.showCloseEventDialog = false
        this.detailVisible = true
      },

      // 是否显示进展按钮（特定事件类型）
      showProgressButton(row) {
        const eventType = String(row.deliveryEventType || row.delivery_event_type)
        return ['31', '32', '33', '35', '38', '40', '41'].includes(eventType)
      },

      // 查看研发任务进展
      handleProgress(row) {
        this.progressEventRow = row
        this.progressDialogVisible = true
      },

      // 作废事件 - 直接弹出作废事件弹窗
      async handleCancelEvent(row) {
        // 校验事件状态
        const status = row.deliveryEventStatus || row.delivery_event_status
        if (String(status) === '30') {
          this.$message.warning('该事件已关闭，无法进行操作')
          return
        }
        if (String(status) === '90') {
          this.$message.warning('该事件已作废，无法进行操作')
          return
        }

        // 获取最新事件详情
        try {
          const res = await deliveryProjectEventApi.getById(row.id)
          if (res.code === 200 && res.data) {
            const latestStatus = res.data.deliveryEventStatus || res.data.delivery_event_status
            if (String(latestStatus) === '30') {
              this.$message.warning('该事件已关闭，无法进行操作')
              this.fetchEventData()
              return
            }
            if (String(latestStatus) === '90') {
              this.$message.warning('该事件已作废，无法进行操作')
              this.fetchEventData()
              return
            }
            this.currentRow = res.data
          } else {
            this.currentRow = row
          }
        } catch (error) {
          console.error('获取事件详情失败:', error)
          this.currentRow = row
        }

        // 直接显示作废事件弹窗
        this.cancelEventDialogVisible = true
      },

      // 删除
      handleDelete(row) {
        this.$confirm('确认删除该记录?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
        })
          .then(async () => {
            try {
              const res = await deliveryProjectEventApi.deleteByIds([row.id])
              if (res.code === 200) {
                this.$message.success('删除成功')
                this.fetchEventData()
              } else {
                this.$message.error(res.msg || '删除失败')
              }
            } catch (error) {
              console.error('删除失败:', error)
              this.$message.error('删除失败')
            }
          })
          .catch(() => {})
      },

      // 行点击
      handleRowClick(row) {
        this.handleView(row)
      },

      // 分页
      handleSizeChange(size) {
        this.queryForm.pageSize = size
        this.fetchData()
      },

      handleCurrentChange(page) {
        this.queryForm.pageNum = page
        this.fetchData()
      },

      // 侧边栏
      toggleSidebar() {
        this.sidebarCollapsed = !this.sidebarCollapsed
      },

      toggleSidebarFilters() {
        this.showSidebarFilters = !this.showSidebarFilters
      },

      selectProject(projectId, project) {
        // 如果点击的是具体项目卡片且状态为待分配(10)，且用户有研发总监/研发主管角色，弹出指派弹窗
        if (project && String(project.status) === '10' && this.canManageDelivery) {
          this.handleAssign(project)
          return
        }
        this.selectedProject = projectId
        this.queryForm.projectId = projectId || ''
        this.queryForm.pageNum = 1
        this.fetchEventData()
      },

      // 处理项目负责人指派（待指派状态）
      handleAssign(project) {
        this.assignDialogData = {
          projectId: project.id,
          projectName: project.name,
          isReassign: false,
          currentDeliveryUserId: null,
          currentDeliveryUserName: null,
        }
        this.assignDialogVisible = true
      },

      // 处理项目负责人改派
      handleReassign(project) {
        this.assignDialogData = {
          projectId: project.id,
          projectName: project.name,
          isReassign: true,
          currentDeliveryUserId: project.deliveryUserId,
          currentDeliveryUserName: project.deliveryOwner,
        }
        this.assignDialogVisible = true
      },

      // 指派/改派成功回调
      handleAssignSuccess() {
        this.fetchProjectList()
      },

      // 查看项目信息
      handleViewProjectInfo(project) {
        if (!project || !project.id) return
        this.selectedProjectId = project.id
        this.projectInfoDialogVisible = true
      },

      formatEventTime(time) {
        return time ? parseTime(time, '{y}-{m}-{d} {h}:{i}') : '-'
      },
      // 产品线标签
      getProductLineLabel(productLine) {
        const label = this.selectDictLabel(this.productLineDict, productLine)
        return label || productLine || '-'
      },

      // 状态样式
      getStatusType(status) {
        const map = {
          10: 'info',
          20: 'primary',
          30: 'warning',
          40: 'success',
          50: 'success',
          90: 'danger',
        }
        return map[status] || 'info'
      },

      // 是否可以删除
      canDelete(status) {
        return ['10', '90'].includes(status)
      },

      // 刷新事件列表
      handleRefreshEvent() {
        this.fetchEventData()
      },

      // 远程搜索负责人
      async remoteFetchOpsUsers(search) {
        this.opsUsersLoading = true
        try {
          const payload = { deptId: DEVOPS_DEV_DEPT_ID, pageNum: 1, pageSize: 999 }
          if (search) payload.keyWords = search
          const res = await userApi.getList(payload)
          const list = res.data?.list || []
          this.opsUserOptions = list.map((u) => ({
            value: u.userId ?? u.user_id ?? u.id ?? null,
            label: u.nickName ?? u.nick_name ?? u.name ?? '',
          }))
        } catch (error) {
          console.error('获取负责人列表失败:', error)
          this.opsUserOptions = []
        } finally {
          this.opsUsersLoading = false
        }
      },
      handleOpsUserVisibleChange(visible) {
        if (visible && !this.opsUsersLoading && !this.opsUserOptions.length) {
          this.remoteFetchOpsUsers('')
        }
      },

      // 多列排序：点击表头切换 无→升序→降序→无
      toggleSort(field) {
        const idx = this.queryForm.sortFields.findIndex((s) => s.field === field)
        if (idx === -1) {
          if (this.queryForm.sortFields.length >= 3) return
          this.queryForm.sortFields.push({ field, order: 'asc' })
        } else if (this.queryForm.sortFields[idx].order === 'asc') {
          this.queryForm.sortFields[idx].order = 'desc'
        } else {
          this.queryForm.sortFields.splice(idx, 1)
        }
        this.fetchEventData()
      },
      getSortState(field) {
        const sf = this.queryForm.sortFields.find((s) => s.field === field)
        return sf ? sf.order : ''
      },
      getSortPriority(field) {
        const idx = this.queryForm.sortFields.findIndex((s) => s.field === field)
        return idx === -1 ? '' : String(idx + 1)
      },
      renderSortableHeader(label, field) {
        const vm = this
        return function (h) {
          const state = vm.getSortState(field)
          const prio = vm.getSortPriority(field)
          return h('div', { class: 'sortable-header', on: { click: () => vm.toggleSort(field) } }, [
            h('span', { class: 'sortable-header-label' }, label),
            h('span', { class: 'sort-arrows' }, [
              h('i', { class: ['el-icon-caret-top', { 'sort-active': state === 'asc' }] }),
              h('i', { class: ['el-icon-caret-bottom', { 'sort-active': state === 'desc' }] }),
            ]),
            prio ? h('span', { class: 'sort-priority' }, prio) : null,
          ])
        }
      },

      // 项目状态筛选变化
      handleProjectStatusChange() {
        this.queryForm.pageNum = 1
        this.projectPageNum = 1
        this.projects = [{ id: '', name: '全部' }]
        this.projectHasMore = true
        this.fetchProjectList()
      },

      // 作废事件弹窗 - 文件上传相关
      handleCancelEventFileChange(file, fileList) {
        this.cancelEventFileList = fileList
        this.cancelEventUploadFiles = fileList.filter((f) => f.raw).map((f) => f.raw)
      },
      handleCancelEventFileRemove(file, fileList) {
        this.cancelEventFileList = fileList
        this.cancelEventUploadFiles = fileList.filter((f) => f.raw).map((f) => f.raw)
      },
      async uploadCancelEventAttachments() {
        const uploadedFiles = []
        for (const file of this.cancelEventUploadFiles) {
          try {
            const result = await uploadFileToRichtextServer(file)
            uploadedFiles.push({
              fileName: result.name || file.name,
              fileUrl: result.url,
              fileType: file.type || 'application/octet-stream',
            })
          } catch (err) {
            this.$message.error(`文件 ${file.name} 上传失败`)
            console.error(err)
            throw err
          }
        }
        return uploadedFiles
      },
      // 取消作废事件弹窗
      handleCancelEventDialogCancel() {
        this.cancelEventDialogVisible = false
        this.cancelEventForm = {
          cancelReason: '',
        }
        this.cancelEventFileList = []
        this.cancelEventUploadFiles = []
      },
      // 保存作废事件
      async handleSaveCancelEvent() {
        this.$refs.cancelEventForm.validate(async (valid) => {
          if (!valid) return

          this.submitLoading = true
          try {
            // 上传附件
            const uploadedAttachments = await this.uploadCancelEventAttachments()

            // 调用作废接口
            const cancelData = {
              id: this.currentRow.id,
              cancelReason: this.cancelEventForm.cancelReason,
              attachments: uploadedAttachments,
            }

            const cancelRes = await deliveryProjectEventApi.cancel(cancelData)
            if (cancelRes.code === 200) {
              this.$message.success('事件作废成功')
              this.handleCancelEventDialogCancel()
              this.fetchEventData()
            } else {
              this.$message.error(cancelRes.msg || '作废事件失败')
            }
          } catch (error) {
            console.error('作废事件失败:', error)
            this.$message.error('作废事件失败')
          } finally {
            this.submitLoading = false
          }
        })
      },
    },
  }
</script>

<style lang="scss" scoped>
  .delivery-project-page {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    min-height: 0;
    padding: 4px;
    height: calc(100vh - 122px);
    background: #f5f7fa;
    overflow: hidden;
  }

  .query-form-container {
    margin-bottom: 4px;
    flex-shrink: 0;
    padding: 8px 12px;
    background: #fff;
    border-radius: 6px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
  }

  .query-form-basic {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;

    .query-form-fields {
      flex: 1;
      min-width: 0;
    }

    .query-form-actions {
      display: flex;
      align-items: center;
      justify-content: flex-end;
      flex-shrink: 0;
      min-height: 32px;
      white-space: nowrap;
    }

    ::v-deep .el-form {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      row-gap: 2px;
    }

    ::v-deep .el-form-item {
      margin-bottom: 2px;
      margin-right: 20px;
    }

    ::v-deep .el-form-item__label {
      padding-right: 8px;
      font-size: 13px;
      color: #606266;
    }

    ::v-deep .el-divider--vertical {
      margin: 0 12px;
    }
  }

  .query-form-advanced {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px dashed #e4e7ed;

    ::v-deep .el-form {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      row-gap: 2px;
    }

    ::v-deep .el-form-item {
      margin-bottom: 2px;
      margin-right: 20px;
    }

    ::v-deep .el-form-item__label {
      padding-right: 8px;
      font-size: 13px;
      color: #606266;
    }
  }

  .table-container {
    display: flex;
    flex: 1;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
    background: #fff;
    border-radius: 4px;
    padding: 6px 8px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
  }

  .table-scroll-wrapper {
    display: flex;
    flex: 1;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
  }

  .delivery-project-table {
    flex: 1;
    min-width: 0;
  }

  .pagination-wrapper {
    margin-top: auto;
    padding-top: 4px;
    display: flex;
    justify-content: flex-end;
  }

  .pagination-wrapper ::v-deep .el-pagination {
    padding: 0;
    line-height: 32px;
  }

  .event-title-text {
    display: inline-block;
    max-width: 100%;
    color: #303133;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .project-sidebar {
    display: flex;
    flex-direction: column;
    width: 280px;
    min-height: 0;
    background: #fff;
    border-radius: 4px;
    padding: 8px;
    flex-shrink: 0;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
    transition: width 0.2s ease, padding 0.2s ease;
    overflow: hidden;

    &.collapsed {
      width: 44px;
      padding: 8px 4px;
      align-items: center;
    }
  }
  .sort-select,
  .search-type-select {
    width: 120px;
  }

  .search-input {
    width: 200px;
  }

  .project-status-filter {
    margin-bottom: 8px;

    ::v-deep .el-radio-group {
      display: flex;
      width: 100%;
    }

    ::v-deep .el-radio-button {
      flex: 1;

      .el-radio-button__inner {
        width: 100%;
        padding: 8px 0;
        font-size: 12px;
      }
    }
  }

  .project-status-tag {
    flex-shrink: 0;
    padding: 2px 8px;
    font-size: 11px;
    color: #fff;
    background: #909399;
    border-radius: 999px;
    white-space: nowrap;

    &--10 {
      background: #909399;
    }

    &--20 {
      background: #409eff;
    }

    &--30 {
      background: #e6a23c;
    }

    &--40 {
      background: #67c23a;
    }

    &--50 {
      background: #1f9d55;
    }

    &--90 {
      background: #f56c6c;
    }
  }

  .main-content {
    display: flex;
    flex: 1;
    gap: 4px;
    min-height: 0;
    overflow: hidden;
  }

  .sidebar-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 6px;
  }

  .sidebar-title-group {
    display: flex;
    align-items: center;
    gap: 6px;
  }

  .sidebar-actions {
    display: flex;
    align-items: center;
  }

  .project-sidebar.collapsed .sidebar-header {
    justify-content: center;
    margin-bottom: 0;
    width: 100%;
  }

  .project-sidebar.collapsed .project-search,
  .project-sidebar.collapsed .project-list {
    display: none;
  }

  .sidebar-title {
    font-size: 14px;
    font-weight: 500;
    color: #303133;
  }

  .sidebar-collapsed-label {
    display: flex;
    flex: 1;
    align-items: center;
    justify-content: flex-start;
    padding-top: 12px;
    writing-mode: vertical-rl;
    text-orientation: upright;
    white-space: nowrap;
    font-size: 14px;
    font-weight: 500;
    color: #606266;
    letter-spacing: 2px;
    user-select: none;
  }

  .collapse-trigger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    background: #f5f7fa;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    color: #909399;
    transition: all 0.2s;
  }

  .sidebar-action-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    background: #f5f7fa;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    color: #909399;
    transition: all 0.2s;

    &:hover {
      background: #ecf5ff;
      color: #409eff;
    }

    &.active {
      background: #ecf5ff;
      color: #409eff;
    }

    i {
      font-size: 12px;
      line-height: 1;
    }
  }

  .project-search {
    margin-bottom: 6px;
  }

  .project-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding-right: 2px;
  }

  .project-list-loading,
  .project-list-end {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 12px 0;
    font-size: 13px;
    color: #909399;
  }

  .project-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 12px;
    cursor: pointer;
    border-radius: 12px;
    transition: all 0.2s;
    border: 1px solid transparent;

    &:hover {
      background: #f8fbff;
    }

    &.active {
      background: linear-gradient(180deg, #eef6ff 0%, #e6f0ff 100%);
      border-color: #bfd9ff;
      box-shadow: 0 8px 18px rgba(64, 158, 255, 0.12);
    }
  }

  .project-item--all {
    margin-bottom: 8px;
    background: linear-gradient(135deg, #f8fbff 0%, #f3f7fd 100%);
    border-color: #e4edf7;
  }

  .project-overview-label {
    font-size: 15px;
    font-weight: 600;
    color: #303133;
  }

  .project-overview-desc {
    font-size: 12px;
    color: #909399;
    margin-top: 2px;
  }

  .project-card {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 98px;
    padding: 8px 10px;
    border-radius: 14px;
    border: 1px solid #ebeef5;
    background: linear-gradient(180deg, #ffffff 0%, #fbfcfe 100%);
    box-shadow: 0 8px 18px rgba(15, 23, 42, 0.05);
    cursor: pointer;
    transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;

    &:not(:last-child) {
      margin-bottom: 8px;
    }

    &:hover {
      transform: translateY(-1px);
      border-color: #d5e7ff;
      box-shadow: 0 12px 22px rgba(64, 158, 255, 0.12);
    }

    &.active {
      border-color: #8bb8ff;
      background: linear-gradient(180deg, #eff6ff 0%, #f7fbff 100%);
      box-shadow: 0 14px 26px rgba(64, 158, 255, 0.16);
    }
  }

  .project-card-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 6px;
  }

  .project-card-tags {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 1;
    min-width: 0;
  }

  .project-contract {
    max-width: 108px;
    padding: 2px 8px;
    font-size: 11px;
    color: #5f6b7a;
    background: #f2f6fc;
    border-radius: 999px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;

    &.clickable {
      cursor: pointer;
      transition: all 0.2s;

      &:hover {
        color: #409eff;
        background: #ecf5ff;
      }
    }
  }

  .project-line-tag {
    flex-shrink: 0;
    padding: 2px 8px;
    font-size: 11px;
    color: #409eff;
    background: #ecf5ff;
    border-radius: 999px;
    white-space: nowrap;
  }

  .project-card-title {
    margin-bottom: 8px;
    font-size: 14px;
    font-weight: 600;
    line-height: 1.3;
    color: #303133;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .project-card-meta {
    display: flex;
    align-items: center;
    gap: 6px;
  }

  .project-card-meta-item {
    display: flex;
    align-items: center;
    flex: 1;
    gap: 6px;
    min-width: 0;
    padding: 4px 8px;
    background: #f7f9fc;
    border-radius: 10px;
  }

  .project-card-meta-icon {
    flex-shrink: 0;
    font-size: 14px;
    color: #409eff;
  }

  .project-card-meta-icon--delivery {
    color: #67c23a;
  }

  .reassign-btn {
    flex-shrink: 0;
    font-size: 12px;
    color: #409eff;
    cursor: pointer;
    margin-left: 4px;
    padding: 2px;
    border-radius: 50%;
    transition: all 0.2s;

    &:hover {
      background: #ecf5ff;
      transform: rotate(180deg);
    }
  }

  .project-card-meta-value {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    color: #606266;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .project-card-check {
    position: absolute;
    top: 12px;
    right: 12px;
    font-size: 14px;
    color: #409eff;
  }

  .el-icon-check {
    font-size: 12px;
    color: #409eff;
  }

  ::v-deep .sortable-header {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;

    .sort-arrows {
      display: inline-flex;
      flex-direction: column;
      line-height: 1;

      i {
        font-size: 10px;
        color: #c0c4cc;
        transition: color 0.2s;

        &.sort-active {
          color: #409eff;
        }
      }
    }

    .sort-priority {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 16px;
      height: 16px;
      border-radius: 50%;
      background: #409eff;
      color: #fff;
      font-size: 10px;
      font-weight: 700;
      line-height: 1;
      margin-left: 2px;
    }
  }

  .query-select--owner {
    width: 180px;
  }
</style>
