订单数据源增加订单对比功能

This commit is contained in:
wuweihua
2026-10-20 13:55:55 +08:00
parent 8093cb52e7
commit 45969a4d29
20 changed files with 1865 additions and 96 deletions
@@ -0,0 +1,218 @@
<template>
<el-drawer
v-model="visible"
size="720px"
:title="drawerTitle"
append-to-body
destroy-on-close
@closed="onClosed"
>
<div v-loading="starting">
<el-descriptions :column="2" border size="small" class="mb12">
<el-descriptions-item label="任务ID" :span="2">{{ asyncTaskId || '-' }}</el-descriptions-item>
<el-descriptions-item label="客户">{{ filterText.customers }}</el-descriptions-item>
<el-descriptions-item label="提货时间">{{ filterText.time }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag size="small" :type="statusTagType">{{ statusLabel }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="进度">{{ progress.percent || 0 }}%</el-descriptions-item>
</el-descriptions>
<el-progress
:percentage="Math.min(100, Math.max(0, Number(progress.percent) || 0))"
:status="progressStatus"
:stroke-width="12"
striped
:striped-flow="running"
class="mb12"
/>
<div class="summary-line">
已扫描 {{ progress.scanned || 0 }}
/ 本地已有 {{ progress.matched || 0 }}
/ 本地缺失 {{ progress.missing || 0 }}
</div>
<div v-if="progress.message" class="msg-line">{{ progress.message }}</div>
<div v-if="progress.error" class="err-line">{{ progress.error }}</div>
<el-alert
class="mb12"
type="info"
:closable="false"
show-icon
title="仅比对转单号:上游 TRANSFER_ORDER_ID 有、本地 transfer_no 没有。异步分页扫描,结果已写入操作记录。"
/>
<div class="json-panel__head">
<span>缺失转单号(样本)</span>
<el-button
type="primary"
link
:disabled="!canFillSearch"
@click="fillToSearch"
>回填到单号搜索</el-button>
</div>
<el-table :data="missingRows" border size="small" max-height="360">
<el-table-column type="index" width="55" label="#" />
<el-table-column prop="transferNo" label="转单号" min-width="220" show-overflow-tooltip />
</el-table>
<el-empty v-if="!running && !(progress.missingTransferNos || []).length" description="暂无缺失转单号" />
</div>
</el-drawer>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { compareOrderSourceByFilter, getOrderSourceCompareProgress } from '@/api/monitor/orderSource'
const emit = defineEmits(['fill-search'])
const visible = ref(false)
const starting = ref(false)
const asyncTaskId = ref('')
const query = ref({})
const progress = ref({})
let timer = null
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) : []
return {
customers: keywords.join(', ') || codes.join(', ') || '-',
time: (q.beginTime || '-') + ' ~ ' + (q.endTime || '-')
}
})
const running = computed(() => {
const st = (progress.value?.status || '').toUpperCase()
return st === 'PENDING' || st === 'RUNNING' || starting.value
})
const drawerTitle = computed(() => {
if (running.value) return '订单比对任务进行中'
const st = (progress.value?.status || '').toUpperCase()
if (st === 'SUCCESS') return '订单比对完成'
if (st === 'FAIL') return '订单比对失败'
return '订单比对任务'
})
const statusLabel = computed(() => {
const st = (progress.value?.status || '').toUpperCase()
if (st === 'PENDING') return '排队中'
if (st === 'RUNNING') return '扫描中'
if (st === 'SUCCESS') return '完成'
if (st === 'FAIL') return '失败'
return st || '-'
})
const statusTagType = computed(() => {
const st = (progress.value?.status || '').toUpperCase()
if (st === 'SUCCESS') return 'success'
if (st === 'FAIL') return 'danger'
if (st === 'RUNNING' || st === 'PENDING') return 'warning'
return 'info'
})
const progressStatus = computed(() => {
const st = (progress.value?.status || '').toUpperCase()
if (st === 'SUCCESS') return 'success'
if (st === 'FAIL') return 'exception'
return undefined
})
const missingRows = computed(() => {
const list = progress.value?.missingTransferNos || []
return list.map(tn => ({ transferNo: tn }))
})
const canFillSearch = computed(() => {
return !running.value && (progress.value?.missingTransferNos || []).length > 0
})
function fillToSearch() {
const list = (progress.value?.missingTransferNos || [])
.map(s => String(s || '').trim())
.filter(Boolean)
if (!list.length) {
ElMessage.warning('暂无可回填的缺失转单号')
return
}
const truncated = !!(progress.value?.missingTruncated) || (Number(progress.value?.missing || 0) > list.length)
emit('fill-search', { transferNos: list, truncated })
visible.value = false
}
function clearTimer() {
if (timer) {
clearInterval(timer)
timer = null
}
}
function poll() {
if (!asyncTaskId.value) return
getOrderSourceCompareProgress(asyncTaskId.value).then(res => {
progress.value = res.data || {}
const st = (progress.value.status || '').toUpperCase()
if (st === 'SUCCESS' || st === 'FAIL') {
clearTimer()
}
}).catch(() => {})
}
function open(q) {
query.value = q || {}
asyncTaskId.value = ''
progress.value = {}
visible.value = true
starting.value = true
clearTimer()
compareOrderSourceByFilter(query.value).then(res => {
asyncTaskId.value = res.data?.asyncTaskId || ''
if (!asyncTaskId.value) {
ElMessage.error('启动比对任务失败')
return
}
progress.value = { status: 'PENDING', percent: 0, message: 'queued', scanned: 0, matched: 0, missing: 0 }
poll()
timer = setInterval(poll, 1500)
}).catch(() => {
ElMessage.error('启动比对任务失败')
}).finally(() => {
starting.value = false
})
}
function onClosed() {
clearTimer()
}
defineExpose({ open })
</script>
<style scoped>
.mb12 { margin-bottom: 12px; }
.summary-line {
margin-bottom: 6px;
font-weight: 600;
}
.msg-line {
margin-bottom: 8px;
color: var(--el-text-color-secondary);
font-size: 12px;
}
.err-line {
margin-bottom: 8px;
color: #f56c6c;
word-break: break-all;
}
.json-panel__head {
margin: 12px 0 6px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
</style>
@@ -109,6 +109,10 @@ function actionLabel(row) {
}
if (action === 'ONE') return '单条同步'
if (action === 'ONE_FAIL') return '同步失败'
if (action === 'COMPARE') return '订单比对'
if (action === 'COMPARE_DONE') {
return row.status === 'FAIL' ? '比对完成(失败)' : '比对完成'
}
return action || '-'
}
@@ -38,6 +38,15 @@
@click="openFilterSync()"
>同步计费</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Search"
v-hasPermi="['monitor:orderSource:list']"
@click="openOrderCompare()"
>订单比对</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
@@ -77,7 +86,7 @@
<template #col-orderStatus="{ row }">
{{ orderStatusLabel(row.orderStatus) }}
</template>
<template #col-action="{ row }">
<!-- <template #col-action="{ row }">
<el-button
link
type="warning"
@@ -86,7 +95,7 @@
v-hasPermi="['monitor:orderSource:sync']"
@click="handleSyncOne(row)"
>同步计费</el-button>
</template>
</template> -->
</basic-table>
<order-source-sync-dialog
@@ -96,25 +105,30 @@
/>
<order-source-sync-log-drawer ref="logDrawerRef" />
<order-source-product-check-drawer ref="productCheckDrawerRef" />
<order-source-compare-drawer ref="compareDrawerRef" @fill-search="onCompareFillSearch" />
</div>
</template>
<script setup name="OrderSource">
import { ElMessage } from 'element-plus'
import { listOrderSource, syncOrderSourceSelected } from '@/api/monitor/orderSource'
import { listOrderSource, listOrderSourceByTransferNos, syncOrderSourceSelected } from '@/api/monitor/orderSource'
import OrderSourceSyncDialog from './components/OrderSourceSyncDialog.vue'
import OrderSourceSyncLogDrawer from './components/OrderSourceSyncLogDrawer.vue'
import OrderSourceProductCheckDrawer from './components/OrderSourceProductCheckDrawer.vue'
import OrderSourceCompareDrawer from './components/OrderSourceCompareDrawer.vue'
const { proxy } = getCurrentInstance()
const tableRef = ref()
const syncDialogRef = ref()
const logDrawerRef = ref()
const productCheckDrawerRef = ref()
const compareDrawerRef = ref()
const checkLoading = ref(false)
const syncingKey = ref(null)
const batchLoading = ref(false)
const syncFloat = ref(null)
/** 比对回填搜索模式:走专用转单号接口,不影响普通列表 */
const transferFillMode = ref(false)
const searchColumns = [
{
@@ -168,8 +182,8 @@ const columns = [
{ label: '计费重', prop: 'dimensionalWeightFactor', align: 'center', width: 100 },
{ label: '体积重', prop: 'volumeWeight', align: 'center', width: 100 },
{ label: '实重', prop: 'weight', align: 'center', width: 100 },
{ label: '提货时间', prop: 'pickupDate', align: 'center', width: 170 },
{ label: '操作', prop: 'action', align: 'center', width: 120, fixed: 'right', slot: 'action', showInToolbar: false }
{ label: '提货时间', prop: 'pickupDate', align: 'center', width: 170 }
// { label: '操作', prop: 'action', align: 'center', width: 120, fixed: 'right', slot: 'action', showInToolbar: false }
]
const orderStatusMap = {
@@ -183,7 +197,8 @@ const orderStatusMap = {
7: '已上网',
8: '已签收',
9: '已退件',
10: '已删除'
10: '已删除',
11: '已复核'
}
const syncFloatVisible = computed(() => {
@@ -247,12 +262,29 @@ function buildFilterQuery(params) {
}
function queryList(params) {
const src = params || tableRef.value?.getQueryParams?.() || {}
const identifiers = splitLines(src.orderIdentifiers)
if (!identifiers.length) transferFillMode.value = false
// 回填搜索:走专用接口,不走普通 /list
if (transferFillMode.value && identifiers.length) {
if (identifiers.length > MAX_ORDER_IDENTIFIERS) {
ElMessage.warning(`单次最多查询 ${MAX_ORDER_IDENTIFIERS} 个单号,当前 ${identifiers.length} 个,请减少后再查`)
return Promise.resolve({ rows: [], total: 0 })
}
const customers = splitLines(src.customerKeywords)
const fillQuery = { orderIdentifiers: identifiers }
if (customers.length) fillQuery.customerKeywords = customers
fillQuery.pageNum = params.pageNum
fillQuery.pageSize = params.pageSize
return listOrderSourceByTransferNos(fillQuery)
}
const query = buildFilterQuery(params)
const identifierCount = (query.orderIdentifiers || []).length
const hasIdentifier = identifierCount > 0
const hasCustomer = !!(query.customerKeywords && query.customerKeywords.length)
const hasTime = !!query.beginTime && !!query.endTime
// 有任一单号即可查询;无单号时必须客户+提货时间
if (!hasIdentifier && !(hasCustomer && hasTime)) {
ElMessage.warning('请输入单号,或选择客户和提货时间')
return Promise.resolve({ rows: [], total: 0 })
@@ -266,6 +298,52 @@ function queryList(params) {
return listOrderSource(query)
}
function openOrderCompare() {
const q = buildFilterQuery()
const hasCustomer = !!(q.customerKeywords && q.customerKeywords.length)
const hasTime = !!q.beginTime && !!q.endTime
if (!hasCustomer || !hasTime) {
ElMessage.warning('订单比对需先输入客户并选择提货时间')
return
}
proxy.$modal.confirm(
'将按当前客户与提货时间,异步分页比对上游转单号与本地订单库(仅转单号),确认继续?',
'二次确认'
).then(() => {
// 比对只按客户+时间;不带单号框内容(回填后可能有数千转单号,带上会把比对拖死)
const compareQuery = { ...q }
delete compareQuery.orderIdentifiers
delete compareQuery.orderId
delete compareQuery.orgOrderId
delete compareQuery.transferOrderId
compareDrawerRef.value?.open(compareQuery)
}).catch(() => {})
}
/** 比对缺失转单号回填到列表「单号」搜索框并查询 */
function onCompareFillSearch(payload) {
const list = (payload && payload.transferNos) || []
if (!list.length) {
ElMessage.warning('暂无可回填的转单号')
return
}
let nos = list
if (nos.length > MAX_ORDER_IDENTIFIERS) {
nos = nos.slice(0, MAX_ORDER_IDENTIFIERS)
ElMessage.warning(`单次最多查询 ${MAX_ORDER_IDENTIFIERS} 个单号,已截取前 ${MAX_ORDER_IDENTIFIERS} 个`)
} else if (payload && payload.truncated) {
ElMessage.warning(`缺失转单号较多,仅回填样本 ${nos.length} 个`)
} else {
ElMessage.success(`已回填 ${nos.length} 个转单号并开始搜索`)
}
transferFillMode.value = true
tableRef.value?.setQueryParams?.({
orderIdentifiers: nos.join('\n'),
pageNum: 1
})
tableRef.value?.handleQuery?.()
}
function openFilterSync() {
const q = buildFilterQuery()
const identifierCount = (q.orderIdentifiers || []).length