PayrollManagementSystem/frontend/src/views/system/tplus/index.vue

115 lines
7.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>