修改同步计费
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
<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 nos = []
|
||||
if (q.orderId) nos.push('订单号 ' + q.orderId)
|
||||
if (q.orgOrderId) nos.push('运单号 ' + q.orgOrderId)
|
||||
if (q.transferOrderId) nos.push('转单号 ' + q.transferOrderId)
|
||||
return {
|
||||
customers: 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() !== '') : []
|
||||
if (!codes.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,
|
||||
beginTime: q.beginTime,
|
||||
endTime: q.endTime,
|
||||
orderId: q.orderId || undefined,
|
||||
orgOrderId: q.orgOrderId || undefined,
|
||||
transferOrderId: q.transferOrderId || undefined,
|
||||
batchSize: 50
|
||||
}).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>
|
||||
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" size="860px" :title="'操作记录'" append-to-body destroy-on-close>
|
||||
<basic-table
|
||||
v-if="visible"
|
||||
:columns="columns"
|
||||
:search-columns="searchColumns"
|
||||
:query="queryList"
|
||||
row-key="docId"
|
||||
:page-size="10"
|
||||
:selection="false"
|
||||
:show-add="false"
|
||||
>
|
||||
<template #col-actionName="{ row }">
|
||||
{{ actionLabel(row) }}
|
||||
</template>
|
||||
<template #col-status="{ row }">
|
||||
<el-tag size="small" :type="row.status === 'FAIL' ? 'danger' : 'success'">{{ row.status === 'FAIL' ? '失败' : '成功' }}</el-tag>
|
||||
</template>
|
||||
<template #col-action="{ row }">
|
||||
<el-button link type="primary" icon="View" @click="handleView(row)">查看</el-button>
|
||||
</template>
|
||||
</basic-table>
|
||||
|
||||
<el-drawer v-model="detailOpen" size="640px" :title="'操作详情'" append-to-body destroy-on-close>
|
||||
<div v-loading="detailLoading">
|
||||
<el-empty v-if="!detail" description="暂无详情" />
|
||||
<div v-else>
|
||||
<el-descriptions :column="2" border size="small" class="mb12">
|
||||
<el-descriptions-item label="业务ID">{{ detail.bizId || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag size="small" :type="detail.status === 'FAIL' ? 'danger' : 'success'">{{ detail.status === 'FAIL' ? '失败' : '成功' }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="写入时间" :span="2">{{ detail.updateTime || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detail.errorMessage" label="错误信息" :span="2">
|
||||
<span class="err-text">{{ detail.errorMessage }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div class="json-panel">
|
||||
<div class="json-panel__head"><span>请求报文</span></div>
|
||||
<pre class="code-pre">{{ formatJson(detail.requestJson) }}</pre>
|
||||
</div>
|
||||
<div class="json-panel">
|
||||
<div class="json-panel__head"><span>返回报文</span></div>
|
||||
<pre class="code-pre">{{ formatJson(detail.responseJson) }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { listOrderSourceSyncLog, getOrderSourceSyncLog } from '@/api/monitor/orderSource'
|
||||
|
||||
const visible = ref(false)
|
||||
const detailOpen = ref(false)
|
||||
const detailLoading = ref(false)
|
||||
const detail = ref(null)
|
||||
|
||||
const searchColumns = [
|
||||
{ label: '业务ID', prop: 'bizId', component: 'input', placeholder: '业务ID', width: '220px' },
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
component: 'select',
|
||||
options: [
|
||||
{ label: '成功', value: 'SUCCESS' },
|
||||
{ label: '失败', value: 'FAIL' }
|
||||
],
|
||||
placeholder: '请选择',
|
||||
width: '140px',
|
||||
props: { clearable: true }
|
||||
}
|
||||
]
|
||||
|
||||
const columns = [
|
||||
{ label: '操作', prop: 'actionName', width: 120, slot: 'actionName' },
|
||||
{ label: '业务ID', prop: 'bizId', minWidth: 180, showOverflowTooltip: true },
|
||||
{ label: '状态', prop: 'status', align: 'center', width: 90, slot: 'status' },
|
||||
{ label: '写入时间', prop: 'updateTime', align: 'center', width: 170 },
|
||||
{ label: '操作', prop: 'action', align: 'center', width: 80, fixed: 'right', slot: 'action', showInToolbar: false }
|
||||
]
|
||||
|
||||
function parseReq(row) {
|
||||
if (!row || !row.requestJson) return {}
|
||||
try {
|
||||
return typeof row.requestJson === 'object' ? row.requestJson : JSON.parse(row.requestJson)
|
||||
} catch (e) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function actionLabel(row) {
|
||||
const action = parseReq(row).action
|
||||
if (action === 'FILTER') return '筛选同步'
|
||||
if (action === 'FILTER_DONE') return '筛选完成'
|
||||
if (action === 'SELECTED') return '同步选中'
|
||||
if (action === 'ONE') return '单条同步'
|
||||
return action || '-'
|
||||
}
|
||||
|
||||
function queryList(params) {
|
||||
return listOrderSourceSyncLog(params)
|
||||
}
|
||||
|
||||
function formatJson(raw) {
|
||||
if (raw == null || raw === '') return '-'
|
||||
try {
|
||||
const obj = typeof raw === 'string' ? JSON.parse(raw) : raw
|
||||
return JSON.stringify(obj, null, 2)
|
||||
} catch (e) {
|
||||
return String(raw)
|
||||
}
|
||||
}
|
||||
|
||||
function handleView(row) {
|
||||
detailOpen.value = true
|
||||
detail.value = null
|
||||
detailLoading.value = true
|
||||
getOrderSourceSyncLog(row.docId).then(res => {
|
||||
detail.value = res.data || row
|
||||
}).finally(() => {
|
||||
detailLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function open() {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.err-text {
|
||||
color: #f56c6c;
|
||||
word-break: break-all;
|
||||
}
|
||||
.json-panel {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.json-panel__head {
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.code-pre {
|
||||
margin: 0;
|
||||
padding: 10px 12px;
|
||||
max-height: 240px;
|
||||
overflow: auto;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user