Files
BMS/bms-front/src/views/monitor/orderSource/components/OrderSourceSyncDialog.vue
T
2026-10-16 10:59:59 +08:00

324 lines
9.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.
<template>
<el-dialog
v-model="visible"
:title="dialogTitle"
width="520px"
append-to-body
destroy-on-close
:close-on-click-modal="!loading"
:close-on-press-escape="!loading"
:show-close="!loading"
@closed="onClosed"
>
<el-form v-show="!loading && !finished" label-width="96px">
<el-form-item label="客户">
<span>{{ filterText.customers }}</span>
</el-form-item>
<el-form-item label="提货时间">
<span>{{ filterText.time }}</span>
</el-form-item>
<el-form-item v-if="filterText.nos" label="单号">
<span>{{ filterText.nos }}</span>
</el-form-item>
<el-alert
type="info"
:closable="false"
show-icon
title="按当前筛选分页查询上游订单并逐单同步计费,避免一次性加载全量。"
/>
</el-form>
<div v-if="loading || finished" class="filter-retry-progress">
<el-progress
:percentage="progressPercent"
:status="progressStatus"
:stroke-width="14"
striped
:striped-flow="loading"
/>
<div class="filter-retry-progress__msg">{{ progressMessage }}</div>
<div class="filter-retry-progress__text">
总数 {{ progress.total || 0 }}
/ 已处理 {{ progress.processed || 0 }}
/ 成功 {{ progress.ok || 0 }}
/ 失败 {{ progress.fail || 0 }}
</div>
<div v-if="progress.error" class="filter-retry-progress__error">{{ progress.error }}</div>
<p v-if="loading" class="filter-retry-progress__hint">请勿关闭页面,大量订单同步计费可能需要一些时间</p>
</div>
<template #footer>
<template v-if="!loading && !finished">
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="submit">开始同步计费</el-button>
</template>
<template v-else-if="finished">
<el-button type="primary" @click="visible = false">关闭</el-button>
</template>
<template v-else>
<el-button disabled>执行中...</el-button>
</template>
</template>
</el-dialog>
</template>
<script setup>
import { syncOrderSourceByFilter, getOrderSourceSyncProgress } from '@/api/monitor/orderSource'
const emit = defineEmits(['success', 'progress'])
const { proxy } = getCurrentInstance()
const visible = ref(false)
const loading = ref(false)
const finished = ref(false)
const query = ref({})
const progress = ref({
percent: 0,
message: '',
total: 0,
processed: 0,
ok: 0,
fail: 0,
status: '',
error: ''
})
const filterText = computed(() => {
const q = query.value || {}
const codes = Array.isArray(q.customerCodes) ? q.customerCodes.filter(Boolean) : []
const keywords = Array.isArray(q.customerKeywords) ? q.customerKeywords.filter(Boolean) : []
const identifiers = Array.isArray(q.orderIdentifiers) ? q.orderIdentifiers.filter(Boolean) : []
const nos = []
if (identifiers.length) nos.push('单号 ' + identifiers.join(', '))
if (q.orderId) nos.push('订单号 ' + q.orderId)
if (q.orgOrderId) nos.push('运单号 ' + q.orgOrderId)
if (q.transferOrderId) nos.push('转单号 ' + q.transferOrderId)
return {
customers: keywords.join(', ') || codes.join(', ') || '-',
time: (q.beginTime || '-') + ' ~ ' + (q.endTime || '-'),
nos: nos.join(' / ')
}
})
const dialogTitle = computed(() => {
if (loading.value) return '异步同步计费进行中'
if (finished.value) return '异步同步计费完成'
return '条件批量同步计费'
})
const progressPercent = computed(() => Math.min(100, Math.max(0, Number(progress.value?.percent) || 0)))
const progressStatus = computed(() => {
const st = (progress.value?.status || '').toUpperCase()
if (st === 'SUCCESS') return 'success'
if (st === 'FAIL') return 'exception'
return undefined
})
const progressMessage = computed(() => mapProgressMessage(progress.value?.message, progress.value))
function mapProgressMessage(message, data) {
const m = message || ''
if (!m || m === 'queued') return '排队中...'
if (m === 'starting') return '正在统计订单...'
if (m === 'no tasks') return '无匹配订单'
if (m === 'failed') return '执行失败'
if (m.startsWith('processing ')) {
return '正在同步计费 ' + m.slice('processing '.length)
}
if (m.startsWith('done ')) {
return '完成:成功 ' + (data?.ok || 0) + ' / 失败 ' + (data?.fail || 0)
}
return m
}
function emitProgress() {
emit('progress', {
loading: loading.value,
finished: finished.value,
percent: progressPercent.value,
status: progress.value?.status || '',
message: progressMessage.value,
total: progress.value?.total || 0,
processed: progress.value?.processed || 0,
ok: progress.value?.ok || 0,
fail: progress.value?.fail || 0,
error: progress.value?.error || ''
})
}
function open(filterQuery, autoStart) {
if (loading.value || finished.value) {
visible.value = true
return
}
query.value = { ...(filterQuery || {}) }
progress.value = {
percent: 0,
message: '',
total: 0,
processed: 0,
ok: 0,
fail: 0,
status: '',
error: ''
}
loading.value = false
finished.value = false
visible.value = true
emitProgress()
if (autoStart) {
submit(true)
}
}
function onClosed() {
if (!loading.value) {
finished.value = false
}
}
function submit(skipConfirm) {
const q = query.value || {}
const codes = Array.isArray(q.customerCodes) ? q.customerCodes.filter(v => v != null && String(v).trim() !== '') : []
const keywords = Array.isArray(q.customerKeywords) ? q.customerKeywords.filter(v => v != null && String(v).trim() !== '') : []
const identifiers = Array.isArray(q.orderIdentifiers)
? q.orderIdentifiers.filter(v => v != null && String(v).trim() !== '')
: []
// 与查询同口径:有单号即可同步;无单号时必须客户 + 提货时间
if (!identifiers.length && ((!codes.length && !keywords.length) || !q.beginTime || !q.endTime)) {
proxy.$modal.msgError('请输入单号,或先输入客户并选择提货时间')
return
}
const start = () => {
loading.value = true
finished.value = false
progress.value = {
percent: 0,
message: 'queued',
total: 0,
processed: 0,
ok: 0,
fail: 0,
status: 'PENDING',
error: ''
}
emitProgress()
return syncOrderSourceByFilter({
customerCodes: codes.length ? codes : undefined,
customerKeywords: keywords.length ? keywords : undefined,
orderIdentifiers: Array.isArray(q.orderIdentifiers) && q.orderIdentifiers.length ? q.orderIdentifiers : undefined,
beginTime: q.beginTime,
endTime: q.endTime,
orderId: q.orderId || undefined,
orgOrderId: q.orgOrderId || undefined,
transferOrderId: q.transferOrderId || undefined,
batchSize: 100
}).then(res => {
const asyncTaskId = res.data?.asyncTaskId || res.data
if (!asyncTaskId) {
throw new Error('asyncTaskId missing')
}
return poll(asyncTaskId)
}).then((data) => {
finished.value = true
emit('success', data)
proxy.$modal.msgSuccess(
'完成:成功 ' + (data.ok || 0) + ' / 失败 ' + (data.fail || 0)
)
}).catch(() => {
if (loading.value) {
finished.value = true
if (!progress.value?.status || progress.value.status === 'PENDING' || progress.value.status === 'RUNNING') {
progress.value = {
...progress.value,
status: 'FAIL',
percent: 100,
message: 'failed',
error: progress.value?.error || '批量同步计费失败'
}
}
}
}).finally(() => {
loading.value = false
emitProgress()
})
}
if (skipConfirm) {
start()
return
}
proxy.$modal.confirm(
'确认按当前筛选分页查询并异步同步计费?',
'二次确认'
).then(() => start()).catch(() => {})
}
function poll(asyncTaskId) {
return new Promise((resolve, reject) => {
const tick = () => {
getOrderSourceSyncProgress(asyncTaskId).then(res => {
const data = res.data || {}
progress.value = {
percent: Number(data.percent) || 0,
message: data.message || '',
total: data.total || 0,
processed: data.processed || 0,
ok: data.ok || 0,
fail: data.fail || 0,
status: data.status || '',
error: data.error || ''
}
emitProgress()
const st = (data.status || '').toUpperCase()
if (st === 'SUCCESS') {
progress.value.percent = 100
resolve(data)
return
}
if (st === 'FAIL') {
proxy.$modal.msgError(data.error || data.message || '批量同步计费失败')
reject(new Error(data.error || 'fail'))
return
}
setTimeout(tick, 1500)
}).catch(err => reject(err))
}
tick()
})
}
defineExpose({ open })
</script>
<style scoped>
.filter-retry-progress {
padding: 8px 4px 0;
}
.filter-retry-progress__msg {
margin-top: 14px;
font-size: 14px;
font-weight: 600;
color: #303133;
line-height: 1.5;
}
.filter-retry-progress__text {
margin-top: 8px;
font-size: 12px;
color: #606266;
line-height: 1.6;
}
.filter-retry-progress__error {
margin-top: 8px;
font-size: 12px;
color: #f56c6c;
word-break: break-all;
}
.filter-retry-progress__hint {
margin: 12px 0 0;
font-size: 12px;
color: #909399;
line-height: 1.5;
}
</style>