yeg
2026-09-06 e6a282cb844d7f5f7ec6c0fb18b643fd6f3d557e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
import { useSpliceReports, type SpliceReportRecord } from '@/stores/spliceReports'
 
interface FilterState {
  reportName: string
  creator: string
  dateRange: [string, string] | null
}
 
const router = useRouter()
const { records, removeReport } = useSpliceReports()
const filters = reactive<FilterState>({ reportName: '', creator: '', dateRange: null })
const appliedFilters = ref<FilterState>({ reportName: '', creator: '', dateRange: null })
const currentPage = ref(1)
const pageSize = ref(10)
 
const creatorOptions = computed(() => [...new Set(records.value.map(item => item.creator))].sort((a, b) => a.localeCompare(b, 'zh-CN')))
const filteredRecords = computed(() => {
  const query = appliedFilters.value
  const keyword = query.reportName.trim().toLowerCase()
  return records.value
    .filter(item => !keyword || item.name.toLowerCase().includes(keyword) || String(item.id).includes(keyword))
    .filter(item => !query.creator || query.creator === '全部' || item.creator === query.creator)
    .filter(item => !query.dateRange || (item.createdAt.slice(0, 10) >= query.dateRange[0] && item.createdAt.slice(0, 10) <= query.dateRange[1]))
    .sort((a, b) => b.createdAt.localeCompare(a.createdAt))
})
const pagedRecords = computed(() => filteredRecords.value.slice((currentPage.value - 1) * pageSize.value, currentPage.value * pageSize.value))
 
function applySearch() {
  appliedFilters.value = {
    reportName: filters.reportName,
    creator: filters.creator,
    dateRange: filters.dateRange ? [...filters.dateRange] as [string, string] : null,
  }
  currentPage.value = 1
  ElMessage.success(`已找到 ${filteredRecords.value.length} 份拼接报表`)
}
 
function resetSearch() {
  Object.assign(filters, { reportName: '', creator: '', dateRange: null })
  appliedFilters.value = { reportName: '', creator: '', dateRange: null }
  currentPage.value = 1
}
 
function openCreate() { router.push('/report-splice/new') }
function openEdit(item: SpliceReportRecord) { router.push(`/report-splice/${item.id}/edit`) }
function openView(item: SpliceReportRecord) { router.push({ path: `/report-splice/${item.id}/edit`, query: { mode: 'view' } }) }
 
async function deleteReport(item: SpliceReportRecord) {
  try {
    await ElMessageBox.confirm(`删除“${item.name}”后无法恢复,是否继续?`, '确认删除拼接报表', {
      confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning', confirmButtonClass: 'danger-confirm',
    })
    removeReport(item.id)
    currentPage.value = Math.min(currentPage.value, Math.max(1, Math.ceil(filteredRecords.value.length / pageSize.value)))
    ElMessage.success('拼接报表已删除')
  } catch {
    // 用户取消删除。
  }
}
</script>
 
<template>
  <div class="splice-report-page">
    <section class="filter-panel" aria-label="拼接报表查询条件">
      <header class="section-heading"><div><i></i><strong>查询报表</strong><span>SPLICE REPORT QUERY</span></div><small>共 {{ records.length }} 份报表</small></header>
      <div class="filter-grid">
        <label><span>拼接名称</span><el-input v-model="filters.reportName" clearable placeholder="输入拼接名称或ID" @keyup.enter="applySearch" /></label>
        <label><span>创建人</span><el-select v-model="filters.creator" clearable placeholder="全部"><el-option label="全部" value="全部" /><el-option v-for="item in creatorOptions" :key="item" :label="item" :value="item" /></el-select></label>
        <label><span>创建时间</span><el-date-picker v-model="filters.dateRange" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" range-separator="至" unlink-panels /></label>
        <div class="filter-actions"><button class="primary-button" type="button" @click="applySearch"><el-icon><Search /></el-icon>查询</button><button class="secondary-button" type="button" @click="resetSearch"><el-icon><RefreshLeft /></el-icon>重置</button></div>
      </div>
    </section>
 
    <section class="result-panel">
      <div class="result-toolbar"><div><i></i><strong>拼接报表列表</strong><span>{{ filteredRecords.length }} RESULTS · 按创建时间倒序</span></div><button class="primary-button" type="button" @click="openCreate"><el-icon><Plus /></el-icon>新建拼接报表</button></div>
      <div class="table-wrap">
        <table>
          <thead><tr><th>拼接ID</th><th>项目拼接名称</th><th>创建时间</th><th>创建人</th><th class="action-column">操作</th></tr></thead>
          <tbody>
            <tr v-for="item in pagedRecords" :key="item.id"><td class="id-cell">#{{ item.id }}</td><td :title="item.name"><strong>{{ item.name }}</strong></td><td>{{ item.createdAt }}</td><td>{{ item.creator }}</td><td class="action-column"><div class="row-actions"><button type="button" @click="openView(item)">查看报表</button><button type="button" @click="openEdit(item)">编辑</button><button class="delete-action" type="button" @click="deleteReport(item)">删除</button></div></td></tr>
            <tr v-if="!pagedRecords.length"><td colspan="5" class="empty-cell">暂无符合条件的拼接报表</td></tr>
          </tbody>
        </table>
      </div>
      <footer class="pagination-bar"><span>显示 {{ pagedRecords.length }} 条,共 {{ filteredRecords.length }} 条</span><el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="[10, 20, 50]" :total="filteredRecords.length" layout="sizes, prev, pager, next" background /></footer>
    </section>
  </div>
</template>
 
<style scoped>
.splice-report-page{width:100%}.primary-button,.secondary-button{height:34px;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:0 13px;border:1px solid transparent;border-radius:3px;font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}.primary-button{color:#fff;background:var(--blue);border-color:var(--blue)}.primary-button:hover{background:#176fcb;border-color:#176fcb}.secondary-button{color:#52606d;background:#fff;border-color:var(--line-strong)}.secondary-button:hover{color:var(--blue);border-color:#9dc8f5}.filter-panel,.result-panel{background:#fff;border:1px solid #d8e1e9}.filter-panel{margin-bottom:16px}.section-heading,.result-toolbar{display:flex;align-items:center;justify-content:space-between;background:var(--surface-soft);border-bottom:1px solid var(--line)}.section-heading{height:44px;padding:0 16px}.section-heading>div,.result-toolbar>div{display:flex;align-items:center;gap:8px}.section-heading>div>i,.result-toolbar>div:first-child>i{width:3px;height:16px;background:#2b78a3}.section-heading strong,.result-toolbar strong{color:var(--ink);font-size:15px}.section-heading span,.result-toolbar span{color:#81909d;font-size:10px}.section-heading small{color:#647380;font-size:11px}.filter-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px 18px;padding:16px}.filter-grid label{width:100%;min-width:0;display:grid;grid-template-columns:72px minmax(0,1fr);align-items:center;gap:8px}.filter-grid label>span{color:#52606d;font-size:12px}.filter-grid :deep(.el-select),.filter-grid :deep(.el-date-editor){width:100%}.filter-grid :deep(.el-input__wrapper),.filter-grid :deep(.el-select__wrapper){min-height:34px;border-radius:3px;box-shadow:0 0 0 1px #d8e1e9 inset;background:#f8fafc}.filter-actions{display:flex;justify-content:flex-end;gap:8px}.result-toolbar{height:50px;padding:0 16px}.table-wrap{overflow-x:auto}table{width:100%;min-width:1000px;table-layout:fixed;border-collapse:collapse;color:#52606d;font-size:13px}th,td{height:50px;padding:0 12px;border-right:1px solid #dde7f0;border-bottom:1px solid #dde7f0;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}th{height:40px;background:#eaf4ff;border-color:#c7e0ff;font-size:12px}th:nth-child(1){width:130px}th:nth-child(2){width:360px}th:nth-child(3){width:200px}th:nth-child(4){width:130px}th:last-child{width:200px}.action-column{position:sticky;right:0;z-index:2;background:#fff;box-shadow:inset 1px 0 0 #b4c7d9,-8px 0 14px rgba(23,49,74,.12);text-align:center}th.action-column{z-index:3;background:#eaf4ff}.row-actions{display:flex;justify-content:center;gap:18px}.row-actions button{border:0;color:var(--blue);background:transparent;font-size:12px;cursor:pointer}.row-actions .delete-action{color:#ad4a4a}.id-cell{color:#647380;font-family:'Cascadia Mono',Consolas,monospace;font-size:12px}td strong{color:#26333f}.empty-cell{height:160px;color:#8795a3;text-align:center}.pagination-bar{min-height:58px;display:flex;align-items:center;justify-content:space-between;padding:8px 14px 8px 16px;border-top:1px solid var(--line)}.pagination-bar>span{color:#7b8794;font-size:11px}@media(max-width:1100px){.filter-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:680px){.filter-grid{grid-template-columns:1fr}.filter-actions{justify-content:stretch}.filter-actions button{flex:1}.section-heading span,.result-toolbar span{display:none}.pagination-bar{align-items:flex-start;flex-direction:column;gap:10px}.result-toolbar{height:auto;min-height:50px;gap:10px;padding-top:8px;padding-bottom:8px}}
</style>