115 lines
7.4 KiB
Vue
115 lines
7.4 KiB
Vue
<script setup lang="ts">
|
||
import { onMounted, ref } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import * as api from '@/api/tplus'
|
||
import AppPagination from '@/components/table/AppPagination.vue'
|
||
import { usePagedList } from '@/composables/usePagedList'
|
||
import { handledError } from '@/utils/error'
|
||
import { formatDateTime } from '@/utils/format'
|
||
import AuditResult from './AuditResult.vue'
|
||
import BatchDetailDialog from './BatchDetailDialog.vue'
|
||
import CreateTaskDialog from './CreateTaskDialog.vue'
|
||
import { scopeLabel, statuses } from './labels'
|
||
|
||
const overview = ref<api.TPlusOverview | null>(null)
|
||
const overviewFailed = ref(false)
|
||
const refreshing = ref(false)
|
||
const creating = ref(false)
|
||
const detailNumber = ref<string | null>(null)
|
||
const status = ref<api.TaskStatus>()
|
||
const taskType = ref<api.TaskType>()
|
||
const retrying = ref<string | null>(null)
|
||
const retryKeys = new Map<string, string>()
|
||
const { records, total, pageNum, pageSize, loading, load, search, onPageChange, onSizeChange } =
|
||
usePagedList<api.TPlusBatch, api.BatchQuery>(api.listBatches, () => ({ status: status.value, task_type: taskType.value }))
|
||
|
||
async function refresh(): Promise<void> {
|
||
if (refreshing.value) return
|
||
refreshing.value = true
|
||
overviewFailed.value = false
|
||
try { overview.value = await api.getOverview() }
|
||
catch (error) { overviewFailed.value = true; handledError(error) }
|
||
finally { refreshing.value = false }
|
||
await load()
|
||
}
|
||
async function retry(row: api.TPlusBatch): Promise<void> {
|
||
if (retrying.value) return
|
||
retrying.value = row.batch_no
|
||
const key = retryKeys.get(row.batch_no) ?? crypto.randomUUID()
|
||
retryKeys.set(row.batch_no, key)
|
||
try {
|
||
const batch = await api.retryTask(row.batch_no, key)
|
||
retryKeys.delete(row.batch_no)
|
||
ElMessage.success('重试任务已排队,原批次和异常记录已保留')
|
||
detailNumber.value = batch.batch_no
|
||
await refresh()
|
||
} catch (error) { handledError(error) }
|
||
finally { retrying.value = null }
|
||
}
|
||
function created(batch: api.TPlusDetail): void {
|
||
detailNumber.value = batch.batch_no
|
||
void refresh()
|
||
}
|
||
function statusType(value: api.TaskStatus): 'success' | 'warning' | 'danger' | 'info' {
|
||
return value === 'SUCCEEDED' ? 'success' : value === 'FAILED' ? 'danger' : value === 'PARTIAL' ? 'warning' : 'info'
|
||
}
|
||
onMounted(refresh)
|
||
</script>
|
||
|
||
<template>
|
||
<div class="tplus-page">
|
||
<header class="page-head">
|
||
<div><h1 class="page-title">T+ 数据同步</h1><p class="page-desc">读取源数据、检查关联,并追踪每次处理结果。</p><div class="waterline" /></div>
|
||
<div class="head-actions"><el-button :loading="refreshing" @click="refresh">刷新</el-button><el-button type="primary" :disabled="!overview?.is_configured || overviewFailed" @click="creating = true">新建任务</el-button></div>
|
||
</header>
|
||
<el-alert type="warning" title="当前仅保存原始数据,未写入工资业务表。SQL Server 2008 R2 同版本验收及业务口径确认仍待完成。" :closable="false" show-icon />
|
||
<el-alert v-if="overviewFailed" class="config-note" type="error" title="概况加载失败,请点击刷新重试" :closable="false" />
|
||
<p v-else class="config-note" role="status">{{ overview?.configuration_message ?? '正在加载配置状态…' }}</p>
|
||
<div v-if="overview" class="task-counts" aria-label="任务数量概况"><span v-for="(label, key) in statuses" :key="key">{{ label }} <b class="cell-num">{{ (overview.status_counts[key] ?? 0).toLocaleString() }}</b></span><span>累计新增快照 <b>{{ overview.total_staged_records.toLocaleString() }}</b></span></div>
|
||
<section class="batch-card" aria-label="同步批次">
|
||
<div class="filter-bar">
|
||
<el-select v-model="status" clearable placeholder="全部状态" aria-label="批次状态" @change="search"><el-option v-for="(label, key) in statuses" :key="key" :label="label" :value="key" /></el-select>
|
||
<el-select v-model="taskType" clearable placeholder="全部任务" aria-label="任务类型" @change="search"><el-option label="读取数据" value="READ" /><el-option label="关联检查" value="AUDIT" /></el-select>
|
||
</div>
|
||
<div class="card card-body"><el-table v-loading="loading" :data="records" empty-text="暂无任务,可新建读取或关联检查" class="data-table">
|
||
<el-table-column label="批次 / 创建时间" min-width="235"><template #default="{ row }"><el-button link type="primary" class="cell-code" @click="detailNumber = row.batch_no">{{ row.batch_no }}</el-button><p class="batch-time">{{ formatDateTime(row.create_time) }}</p></template></el-table-column>
|
||
<el-table-column label="任务 / 范围" min-width="180"><template #default="{ row }"><b>{{ row.task_type === 'AUDIT' ? '关联检查' : '读取数据' }}</b><p class="batch-scope">{{ scopeLabel(row) }}</p></template></el-table-column>
|
||
<el-table-column label="状态" width="110"><template #default="{ row }"><el-tag :type="statusType(row.status)">{{ statuses[row.status as api.TaskStatus] }}</el-tag></template></el-table-column>
|
||
<el-table-column prop="source_record_count" label="源记录" width="100" align="right" />
|
||
<el-table-column prop="staged_record_count" label="新增" width="90" align="right" />
|
||
<el-table-column prop="skipped_record_count" label="重复跳过" width="100" align="right" />
|
||
<el-table-column prop="failed_record_count" label="异常" width="80" align="right" />
|
||
<el-table-column label="操作" width="140" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="detailNumber = row.batch_no">查看</el-button><el-button v-if="row.can_retry" link type="primary" :disabled="!overview?.is_configured || overviewFailed || !!retrying" :loading="retrying === row.batch_no" @click="retry(row)">重试</el-button></template></el-table-column>
|
||
</el-table>
|
||
<div class="pager-scroll"><AppPagination :total="total" :page-num="pageNum" :page-size="pageSize" @page-change="onPageChange" @size-change="onSizeChange" /></div></div>
|
||
</section>
|
||
<section class="card card-body latest-audit" aria-label="最近关联检查">
|
||
<h2>最近关联检查</h2>
|
||
<AuditResult v-if="overview?.latest_audit" :report="overview.latest_audit" />
|
||
<p v-else class="page-desc">暂无关联检查结果。新建“关联检查”任务后,可在此查看覆盖数量与待确认口径。</p>
|
||
</section>
|
||
<CreateTaskDialog v-model="creating" :configured="!!overview?.is_configured && !overviewFailed" @created="created" />
|
||
<BatchDetailDialog v-model="detailNumber" />
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.page-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
|
||
.head-actions { display: flex; gap: 8px; }
|
||
.head-actions .el-button + .el-button { margin-left: 0; }
|
||
.config-note { margin: 12px 0; color: var(--ys-ink-2); }
|
||
.task-counts { display: flex; flex-wrap: wrap; gap: 12px 24px; margin: 16px 0; color: var(--ys-ink-2); }
|
||
.task-counts b { color: var(--ys-ink); margin-left: 4px; }
|
||
.batch-card { margin-top: 16px; }
|
||
.filter-bar .el-select { width: 160px; }
|
||
.batch-time, .batch-scope { margin: 4px 0 0; color: var(--ys-ink-2); font-size: 12px; }
|
||
.latest-audit { margin-top: 20px; }
|
||
.latest-audit h2 { margin: 0 0 12px; font-size: 16px; }
|
||
.pager-scroll { overflow-x: auto; }
|
||
@media (max-width: 640px) {
|
||
.page-head { align-items: flex-start; gap: 16px; flex-direction: column; }
|
||
.filter-bar { flex-wrap: wrap; }
|
||
.filter-bar .el-select { width: 100%; }
|
||
}
|
||
</style>
|