Files
BMS/bms-front/src/views/monitor/uuidToNew/index.vue
T

330 lines
9.5 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.
<template>
<div class="app-container uuid-to-new-page">
<el-card shadow="never" class="progress-card">
<div class="progress-head">
<div class="title">UUID 阴影同步进度</div>
<div class="actions">
<span class="switch-label">线程</span>
<el-select
v-model="threadCount"
size="small"
style="width: 88px"
:disabled="configLocked || configLoading"
v-hasPermi="['monitor:uuidToNew:switch']"
@change="onConfigChange"
>
<el-option v-for="n in threadOptions" :key="n" :label="n + '线程'" :value="n" />
</el-select>
<span class="switch-label">批次</span>
<el-select
v-model="batchSize"
size="small"
style="width: 100px"
:disabled="configLocked || configLoading"
v-hasPermi="['monitor:uuidToNew:switch']"
@change="onConfigChange"
>
<el-option v-for="n in batchOptions" :key="n" :label="String(n)" :value="n" />
</el-select>
<span class="switch-label">连续同步</span>
<el-switch
v-model="switchOn"
:loading="switchLoading"
:disabled="switchLoading"
inline-prompt
active-text="开"
inactive-text="关"
v-hasPermi="['monitor:uuidToNew:switch']"
@change="onSwitchChange"
/>
<el-tag :type="progress.running ? 'success' : (progress.enabled ? 'warning' : 'info')" size="small">
{{ statusText }}
</el-tag>
<el-button size="small" icon="Refresh" :loading="progressLoading" @click="refreshAll">刷新</el-button>
</div>
</div>
<el-row :gutter="16" class="stat-row">
<el-col :xs="12" :sm="8" :md="4">
<div class="stat">
<div class="num">{{ formatNum(progress.oldCnt) }}</div>
<div class="label">旧表订单</div>
</div>
</el-col>
<el-col :xs="12" :sm="8" :md="4">
<div class="stat">
<div class="num ok">{{ formatNum(progress.doneCnt) }}</div>
<div class="label">已同步 SUCCESS</div>
</div>
</el-col>
<el-col :xs="12" :sm="8" :md="4">
<div class="stat">
<div class="num warn">{{ formatNum(progress.pendingCnt) }}</div>
<div class="label">积压</div>
</div>
</el-col>
<el-col :xs="12" :sm="8" :md="4">
<div class="stat">
<div class="num">{{ formatNum(progress.newCnt) }}</div>
<div class="label">阴影订单行</div>
</div>
</el-col>
<el-col :xs="12" :sm="8" :md="4">
<div class="stat">
<div class="num">{{ progress.donePercent }}%</div>
<div class="label">完成比例</div>
</div>
</el-col>
<el-col :xs="12" :sm="8" :md="4">
<div class="stat">
<div class="num cfg">{{ progress.threadCount }} / {{ progress.batchSize }}</div>
<div class="label">线程 / 批次</div>
</div>
</el-col>
</el-row>
<el-progress
:percentage="Math.min(100, Number(progress.donePercent) || 0)"
:stroke-width="14"
:text-inside="true"
/>
<div class="hint-line">关闭开关后可改线程/批次;打开后按当前选项立即连续执行,一批结束立刻下一批。开关/线程/批次会记入 Redis,服务重启后自动恢复。</div>
</el-card>
<basic-table
ref="tableRef"
class="job-log-table"
:columns="columns"
:search-columns="searchColumns"
:query="queryList"
:selection="false"
show-index
>
<template #toolbar>
<el-col :span="1.5">
<el-button type="primary" plain icon="Refresh" @click="refreshAll">刷新列表</el-button>
</el-col>
</template>
<template #col-fail="{ row }">
<span :class="{ 'fail-num': row.fail > 0 }">{{ row.fail }}</span>
</template>
<template #col-cost="{ row }">
{{ formatCost(row.startedAt, row.finishedAt) }}
</template>
</basic-table>
</div>
</template>
<script setup name="UuidToNewMonitor">
import { getUuidToNewProgress, listUuidToNewJobLog, setUuidToNewEnabled, updateUuidToNewConfig } from '@/api/monitor/uuidToNew'
const { proxy } = getCurrentInstance()
const tableRef = ref()
const progressLoading = ref(false)
const switchLoading = ref(false)
const configLoading = ref(false)
const switchOn = ref(false)
const threadCount = ref(2)
const batchSize = ref(200)
const threadOptions = [2, 4, 6, 8, 10, 12, 14, 16]
const batchOptions = [50, 100, 200, 300, 500, 1000]
const progress = reactive({
oldCnt: 0,
doneCnt: 0,
newCnt: 0,
pendingCnt: 0,
donePercent: 0,
enabled: false,
running: false,
batchSize: 200,
threadCount: 2
})
const configLocked = computed(() => !!(switchOn.value || progress.enabled || progress.running))
const statusText = computed(() => {
if (progress.running) return '正在同步'
if (progress.enabled) return '开启中(等待启动)'
return '已停止'
})
const searchColumns = [
{ label: '批次号', prop: 'batchNo', component: 'input', placeholder: '批次号', width: '220px' },
{ label: '开始时间', prop: 'dateRange', component: 'datetimerange', width: '360px' }
]
const columns = [
{ label: 'ID', prop: 'id', width: 90, align: 'center' },
{ label: '批次号', prop: 'batchNo', minWidth: 200, showOverflowTooltip: true },
{ label: '抓取', prop: 'fetched', width: 90, align: 'center' },
{ label: '成功', prop: 'success', width: 90, align: 'center' },
{ label: '失败', prop: 'fail', width: 90, align: 'center', slot: 'fail' },
{ label: '跳过', prop: 'skip', width: 90, align: 'center' },
{ label: '耗时', prop: 'cost', width: 110, align: 'center', slot: 'cost' },
{ label: '开始', prop: 'startedAt', width: 170, align: 'center', parseTime: true },
{ label: '结束', prop: 'finishedAt', width: 170, align: 'center', parseTime: true },
{ label: '备注', prop: 'remark', minWidth: 140, showOverflowTooltip: true }
]
function formatNum(v) {
const n = Number(v || 0)
return n.toLocaleString()
}
function formatCost(start, end) {
if (!start || !end) return '-'
const a = new Date(start).getTime()
const b = new Date(end).getTime()
if (!a || !b || b < a) return '-'
const sec = Math.round((b - a) / 1000)
if (sec < 60) return sec + 's'
return Math.floor(sec / 60) + 'm' + (sec % 60) + 's'
}
function syncLocalFromProgress() {
if (progress.threadCount) threadCount.value = Number(progress.threadCount)
if (progress.batchSize) batchSize.value = Number(progress.batchSize)
switchOn.value = !!progress.enabled
}
async function loadProgress() {
progressLoading.value = true
try {
const res = await getUuidToNewProgress()
Object.assign(progress, res.data || {})
syncLocalFromProgress()
} finally {
progressLoading.value = false
}
}
async function onConfigChange() {
if (configLocked.value) {
syncLocalFromProgress()
return
}
configLoading.value = true
try {
const res = await updateUuidToNewConfig({
threadCount: threadCount.value,
batchSize: batchSize.value
})
Object.assign(progress, res.data || {})
syncLocalFromProgress()
} catch (e) {
syncLocalFromProgress()
} finally {
configLoading.value = false
}
}
async function onSwitchChange(val) {
switchLoading.value = true
try {
const res = await setUuidToNewEnabled({
enabled: val,
threadCount: threadCount.value,
batchSize: batchSize.value
})
Object.assign(progress, res.data || {})
syncLocalFromProgress()
proxy.$modal.msgSuccess(val ? '已开启,按当前线程/批次连续同步' : '已关闭,当前批结束后停止')
tableRef.value && tableRef.value.refresh()
} catch (e) {
switchOn.value = !val
} finally {
switchLoading.value = false
}
}
function queryList(params) {
const query = {
pageNum: params.pageNum,
pageSize: params.pageSize,
batchNo: params.batchNo
}
if (params.dateRange && params.dateRange.length === 2) {
query.beginStartedAt = params.dateRange[0]
query.endStartedAt = params.dateRange[1]
}
return listUuidToNewJobLog(query)
}
function refreshAll() {
loadProgress()
tableRef.value && tableRef.value.refresh()
}
let timer = null
onMounted(() => {
loadProgress()
timer = setInterval(() => {
if (progress.enabled || progress.running) {
loadProgress()
tableRef.value && tableRef.value.refresh()
}
}, 5000)
})
onBeforeUnmount(() => {
if (timer) clearInterval(timer)
})
</script>
<style scoped>
.progress-card {
margin-bottom: 16px;
}
.progress-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
gap: 12px;
flex-wrap: wrap;
}
.progress-head .title {
font-size: 16px;
font-weight: 600;
}
.progress-head .actions {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.switch-label {
color: #606266;
font-size: 13px;
}
.stat-row {
margin-bottom: 12px;
}
.stat {
background: #f7f8fa;
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
}
.stat .num {
font-size: 20px;
font-weight: 700;
line-height: 1.2;
}
.stat .num.ok { color: #67c23a; }
.stat .num.warn { color: #e6a23c; }
.stat .num.cfg { font-size: 18px; }
.stat .label {
margin-top: 4px;
color: #909399;
font-size: 12px;
}
.hint-line {
margin-top: 8px;
color: #909399;
font-size: 12px;
}
.fail-num {
color: #f56c6c;
font-weight: 600;
}
</style>