增加计费列表收货时间查询

This commit is contained in:
wuweihua
2026-11-06 16:13:03 +08:00
parent a2e986fa6a
commit 709c315971
10 changed files with 517 additions and 52 deletions
+275 -24
View File
@@ -1,5 +1,20 @@
<template>
<div class="app-container billing-bill">
<div v-if="exportFloatVisible" class="retry-status-float export-status-float">
<el-tooltip :content="exportFloatTooltip" placement="bottom">
<el-button class="retry-status-button" type="warning" plain size="small" @click="openExportProgress()">
<span class="retry-progress-ring" :style="{ '--retry-progress': exportProgressDegree + 'deg' }" aria-hidden="true">
<span class="retry-progress-value">{{ exportProgressText }}</span>
</span>
<span class="retry-status-text">
<strong>{{ exportFloatTitle }}</strong>
<small>{{ exportFloatSummary }}</small>
</span>
</el-button>
</el-tooltip>
</div>
<basic-table
ref="tableRef"
:columns="columns"
@@ -157,6 +172,8 @@
type="warning"
plain
icon="Download"
:loading="exportLoading"
:disabled="exportLoading"
v-hasPermi="['price:billing:export']"
@click="handleExport"
>导出</el-button>
@@ -266,7 +283,33 @@
</div>
</el-dialog>
<excel-export-field-dialog
<el-dialog
v-model="exportProgressVisible"
:title="exportDialogTitle"
width="480px"
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="true"
:show-close="true"
>
<el-progress
:percentage="exportProgressPercent"
:status="exportProgressStatus"
:stroke-width="14"
striped
:striped-flow="exportLoading"
/>
<div class="export-progress__msg">{{ exportProgressMessage }}</div>
<div class="export-progress__text">
导出行数 {{ exportProgressRowCount }}
</div>
<p v-if="exportLoading" class="export-progress__hint">导出在后台进行,可收起弹窗继续操作;请勿关闭或刷新本页</p>
<template #footer>
<el-button v-if="exportLoading" @click="exportProgressVisible = false">收起</el-button>
<el-button v-else type="primary" @click="exportProgressVisible = false">关闭</el-button>
</template>
</el-dialog>
<excel-export-field-dialog
ref="exportFieldRef"
:fields="BILLING_BILL_EXPORT_FIELDS"
storage-key="exportFields:billingBill"
@@ -319,6 +362,78 @@ const productOptions = computed(() =>
.map(p => ({ code: p.productCode, name: p.productName || p.productCode }))
)
const exportLoading = ref(false)
const exportFinished = ref(false)
const exportProgressVisible = ref(false)
const exportProgressPercent = ref(0)
const exportProgressMessage = ref('')
const exportProgressStatus = ref(undefined)
const exportProgressRowCount = ref(0)
const exportTaskId = ref('')
let exportFloatHideTimer = null
const exportFloatVisible = computed(() => exportLoading.value || exportFinished.value)
const exportProgressDegree = computed(() => Math.round(((exportProgressPercent.value || 0) / 100) * 360))
const exportProgressText = computed(() => (exportProgressPercent.value || 0) + '%')
const exportDialogTitle = computed(() => {
if (exportLoading.value) return '异步导出进行中'
if (exportFinished.value) return '异步导出完成'
return '异步导出'
})
const exportFloatTitle = computed(() => {
if (exportLoading.value) return '导出中'
if (exportProgressStatus.value === 'exception') return '导出失败'
if (exportFinished.value) return '导出完成'
return '导出'
})
const exportFloatSummary = computed(() => {
const pct = exportProgressPercent.value || 0
const rows = exportProgressRowCount.value || 0
if (rows > 0) return pct + '% / ' + rows + ' 行'
return pct + '%'
})
const exportFloatTooltip = computed(() => exportProgressMessage.value || '点击查看详情')
function openExportProgress() {
exportProgressVisible.value = true
}
function scheduleHideExportFloat() {
if (exportFloatHideTimer) {
clearTimeout(exportFloatHideTimer)
exportFloatHideTimer = null
}
exportFloatHideTimer = window.setTimeout(() => {
if (!exportLoading.value && exportFinished.value) {
exportFinished.value = false
}
exportFloatHideTimer = null
}, 8000)
}
function applyExportProgress(t) {
const pct = Math.min(100, Number(t && t.percent) || 0)
exportProgressPercent.value = pct
exportProgressRowCount.value = Number(t && t.rowCount) || 0
const st = String((t && t.status) || '').toUpperCase()
if (st === 'PENDING') {
exportProgressMessage.value = (t && t.message) || '排队中...'
exportProgressStatus.value = undefined
} else if (st === 'RUNNING') {
exportProgressMessage.value = (t && t.message) || ('导出中 ' + pct + '%')
exportProgressStatus.value = undefined
} else if (st === 'SUCCESS') {
exportProgressMessage.value = (t && t.message) || '导出完成,正在下载'
exportProgressStatus.value = 'success'
exportProgressPercent.value = 100
} else if (st === 'FAIL' || st === 'FAILED') {
exportProgressMessage.value = (t && (t.error || t.message)) || '导出失败'
exportProgressStatus.value = 'exception'
} else {
exportProgressMessage.value = (t && t.message) || ''
}
}
const tableRef = ref()
const exportFieldRef = ref()
const tableHeight = ref(480)
@@ -457,7 +572,7 @@ const columns = [
{ label: '额度欠费', prop: 'creditArrearsStatus', align: 'center', width: 100, slot: 'creditArrearsStatus' },
// { label: '退费状态', prop: 'refundStatus', align: 'center', width: 100, slot: 'refundStatus' },
{ label: '计费时间', prop: 'bizDate', align: 'center', width: 170, parseTime: true },
{ label: '提货时间', prop: 'pickupDate', align: 'center', width: 170, parseTime: true },
{ label: '收货时间', prop: 'pickupDate', align: 'center', width: 170, parseTime: true },
{ label: '销售员', prop: 'belongSaler', minWidth: 100, showOverflowTooltip: true },
{ label: '结算员', prop: 'settlementMan', minWidth: 100, showOverflowTooltip: true },
{ label: '客服员', prop: 'serviceMan', minWidth: 100, showOverflowTooltip: true },
@@ -530,20 +645,35 @@ const searchColumns = [
// 只选日期时自动补全为当天 00:00:00 ~ 23:59:59
defaultTime: [new Date(2000, 0, 1, 0, 0, 0), new Date(2000, 0, 1, 23, 59, 59)]
}
},
{
label: '收货时间',
prop: 'pickupDateRange',
component: 'datetimerange',
colSpan: 2,
props: {
clearable: true,
defaultTime: [new Date(2000, 0, 1, 0, 0, 0), new Date(2000, 0, 1, 23, 59, 59)]
}
}
]
function buildBillQuery(params) {
const { bizDateRange, orderIdentifiers: rawOrderIdentifiers, ...rest } = params || {}
const { bizDateRange, pickupDateRange, orderIdentifiers: rawOrderIdentifiers, ...rest } = params || {}
const query = { ...rest }
const orderIdentifiers = parseOrderIdentifiers(rawOrderIdentifiers)
if (orderIdentifiers.length) query.orderIdentifiers = orderIdentifiers
const rangeParams = {}
if (Array.isArray(bizDateRange) && (bizDateRange[0] || bizDateRange[1])) {
query.params = {
...(query.params || {}),
beginBizDate: bizDateRange[0] || null,
endBizDate: bizDateRange[1] || null
}
rangeParams.beginBizDate = bizDateRange[0] || null
rangeParams.endBizDate = bizDateRange[1] || null
}
if (Array.isArray(pickupDateRange) && (pickupDateRange[0] || pickupDateRange[1])) {
rangeParams.beginPickupDate = pickupDateRange[0] || null
rangeParams.endPickupDate = pickupDateRange[1] || null
}
if (Object.keys(rangeParams).length) {
query.params = { ...(query.params || {}), ...rangeParams }
}
return query
}
@@ -557,21 +687,66 @@ function queryList(params) {
}
async function handleExport() {
if (exportLoading.value) return
const params = tableRef.value?.getQueryParams?.() || {}
if (!validateOrderIdentifierLimit(params.orderIdentifiers)) return
const fields = await exportFieldRef.value?.open?.()
if (!fields || !fields.length) return
return exportAsyncFromTable({
baseUrl: '/price/billing/bill/export',
tableRef: tableRef.value,
normalize: (raw) => {
const q = buildBillQuery(raw)
q.exportFields = fields
return q
},
filePrefix: 'billing_bill',
requireFilter: true
})
if (exportFloatHideTimer) {
clearTimeout(exportFloatHideTimer)
exportFloatHideTimer = null
}
exportLoading.value = true
exportFinished.value = false
exportProgressVisible.value = false
exportProgressPercent.value = 0
exportProgressRowCount.value = 0
exportProgressStatus.value = undefined
exportProgressMessage.value = '导出任务已提交'
exportTaskId.value = ''
try {
const result = await exportAsyncFromTable({
baseUrl: '/price/billing/bill/export',
tableRef: tableRef.value,
normalize: (raw) => {
const q = buildBillQuery(raw)
q.exportFields = fields
return q
},
filePrefix: 'billing_bill',
requireFilter: true,
showLoading: false,
showSuccessMessage: true,
showErrorMessage: true,
onTaskId: (id) => { exportTaskId.value = id },
onProgress: (task) => applyExportProgress(task || {})
})
if (result && result.task) {
applyExportProgress({ ...(result.task || {}), status: 'SUCCESS', percent: 100 })
exportFinished.value = true
scheduleHideExportFloat()
} else if (!exportTaskId.value) {
exportFinished.value = false
exportProgressPercent.value = 0
exportProgressMessage.value = ''
exportProgressStatus.value = undefined
} else if (exportProgressStatus.value !== 'exception') {
exportProgressStatus.value = 'exception'
exportProgressMessage.value = exportProgressMessage.value || '导出失败'
exportFinished.value = true
scheduleHideExportFloat()
} else {
exportFinished.value = true
scheduleHideExportFloat()
}
} catch (e) {
exportProgressStatus.value = 'exception'
exportProgressMessage.value = (e && e.message) || '导出失败'
exportFinished.value = true
scheduleHideExportFloat()
} finally {
exportLoading.value = false
}
}
@@ -645,14 +820,15 @@ function hasBatchFeeQuery(q) {
|| hasArr(q.productCodes)
|| hasArr(q.billingFailStatuses)
|| hasArr(q.bizDateRange)
|| hasArr(q.pickupDateRange)
|| !!(q.creditArrearsStatus && String(q.creditArrearsStatus).trim())
}
function openBatchFee() {
const q = tableRef.value?.getQueryParams?.() || {}
if (!validateOrderIdentifierLimit(q.orderIdentifiers)) return
const { pageNum, pageSize, bizDateRange, ...rest } = q
if (!hasBatchFeeQuery({ ...rest, bizDateRange })) {
const { pageNum, pageSize, bizDateRange, pickupDateRange, ...rest } = q
if (!hasBatchFeeQuery({ ...rest, bizDateRange, pickupDateRange })) {
ElMessage.warning('请先设置列表查询条件')
return
}
@@ -664,6 +840,10 @@ function openBatchFee() {
listQuery.beginBizDate = bizDateRange[0] || null
listQuery.endBizDate = bizDateRange[1] || null
}
if (Array.isArray(pickupDateRange) && (pickupDateRange[0] || pickupDateRange[1])) {
listQuery.beginPickupDate = pickupDateRange[0] || null
listQuery.endPickupDate = pickupDateRange[1] || null
}
batchFeeListQuery.value = listQuery
batchFeeSelectedIds.value = tableRef.value?.getSelectionIds?.() || []
batchFeeOpen.value = true
@@ -676,8 +856,8 @@ function onBatchFeeSuccess() {
function buildRecalcPayload() {
const q = tableRef.value?.getQueryParams?.() || {}
if (!validateOrderIdentifierLimit(q.orderIdentifiers)) return null
const { pageNum, pageSize, bizDateRange, ...rest } = q
if (!hasBatchFeeQuery({ ...rest, bizDateRange })) {
const { pageNum, pageSize, bizDateRange, pickupDateRange, ...rest } = q
if (!hasBatchFeeQuery({ ...rest, bizDateRange, pickupDateRange })) {
ElMessage.warning('请先设置列表查询条件')
return null
}
@@ -699,6 +879,10 @@ function buildRecalcPayload() {
payload.beginBizDate = bizDateRange[0] || null
payload.endBizDate = bizDateRange[1] || null
}
if (Array.isArray(pickupDateRange) && (pickupDateRange[0] || pickupDateRange[1])) {
payload.beginPickupDate = pickupDateRange[0] || null
payload.endPickupDate = pickupDateRange[1] || null
}
return payload
}
@@ -775,7 +959,7 @@ function onChargeWeightUpdated() {
function buildChargeWeightTemplateQuery() {
const q = tableRef.value?.getQueryParams?.() || {}
const { pageNum, pageSize, bizDateRange, ...rest } = q
const { pageNum, pageSize, bizDateRange, pickupDateRange, ...rest } = q
const payload = {
fillByFilter: !!cwFillByFilter.value,
orderNos: rest.orderNos || [],
@@ -791,6 +975,10 @@ function buildChargeWeightTemplateQuery() {
payload.beginBizDate = bizDateRange[0] || null
payload.endBizDate = bizDateRange[1] || null
}
if (Array.isArray(pickupDateRange) && (pickupDateRange[0] || pickupDateRange[1])) {
payload.beginPickupDate = pickupDateRange[0] || null
payload.endPickupDate = pickupDateRange[1] || null
}
return payload
}
@@ -999,4 +1187,67 @@ function refundTagType(status) {
width: 100%;
}
.order-identifier-search { width: 100%; }
.billing-bill .retry-status-float {
position: fixed;
top: 92px;
right: 24px;
z-index: 3001;
}
.billing-bill .retry-status-button {
display: inline-flex;
align-items: center;
gap: 10px;
min-width: 200px;
min-height: 68px;
padding: 6px 16px 6px 7px;
border-radius: 36px;
}
.billing-bill .retry-progress-ring {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 54px;
width: 54px;
height: 54px;
border-radius: 50%;
background: conic-gradient(
var(--el-color-warning) 0deg var(--retry-progress, 45deg),
var(--el-color-warning-light-8) var(--retry-progress, 45deg) 360deg
);
}
.billing-bill .retry-progress-ring::after {
position: absolute;
inset: 5px;
content: '';
border-radius: 50%;
background: var(--el-bg-color);
}
.billing-bill .retry-progress-value {
position: relative;
z-index: 1;
color: var(--el-color-warning);
font-size: 12px;
font-weight: 600;
line-height: 1;
}
.billing-bill .retry-status-text {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 3px;
line-height: 1.2;
white-space: nowrap;
}
.billing-bill .retry-status-text strong {
font-size: 14px;
font-weight: 600;
}
.billing-bill .retry-status-text small {
color: var(--el-text-color-secondary);
font-size: 11px;
}
.export-progress__msg { margin-top: 12px; color: var(--el-text-color-regular); }
.export-progress__text { margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary); }
.export-progress__hint { margin-top: 12px; font-size: 12px; color: var(--el-color-warning); }
</style>
+177 -11
View File
@@ -1,5 +1,19 @@
<template>
<div class="app-container order-bill-query">
<div v-if="exportFloatVisible" class="retry-status-float export-status-float" :class="{ 'is-offset': rebillFloatVisible }">
<el-tooltip :content="exportFloatTooltip" placement="bottom">
<el-button class="retry-status-button" type="warning" plain size="small" @click="openExportProgress()">
<span class="retry-progress-ring" :style="{ '--retry-progress': exportProgressDegree + 'deg' }" aria-hidden="true">
<span class="retry-progress-value">{{ exportProgressText }}</span>
</span>
<span class="retry-status-text">
<strong>{{ exportFloatTitle }}</strong>
<small>{{ exportFloatSummary }}</small>
</span>
</el-button>
</el-tooltip>
</div>
<div v-if="rebillFloatVisible" class="retry-status-float">
<el-tooltip :content="rebillFloatTooltip" placement="bottom">
<el-button class="retry-status-button" type="primary" plain size="small" @click="openRebillProgress()">
@@ -58,6 +72,8 @@
type="warning"
plain
icon="Download"
:loading="exportLoading"
:disabled="exportLoading"
v-hasPermi="['price:orderBill:export']"
@click="handleExport"
>导出</el-button>
@@ -208,6 +224,32 @@
<el-button v-else type="primary" @click="rebillProgressVisible = false">关闭</el-button>
</template>
</el-dialog>
<el-dialog
v-model="exportProgressVisible"
:title="exportDialogTitle"
width="480px"
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="true"
:show-close="true"
>
<el-progress
:percentage="exportProgressPercent"
:status="exportProgressStatus"
:stroke-width="14"
striped
:striped-flow="exportLoading"
/>
<div class="rebill-progress__msg">{{ exportProgressMessage }}</div>
<div class="rebill-progress__text">
导出行数 {{ exportProgressRowCount }}
</div>
<p v-if="exportLoading" class="rebill-progress__hint">导出在后台进行,可收起弹窗继续操作;请勿关闭或刷新本页</p>
<template #footer>
<el-button v-if="exportLoading" @click="exportProgressVisible = false">收起</el-button>
<el-button v-else type="primary" @click="exportProgressVisible = false">关闭</el-button>
</template>
</el-dialog>
<order-source-sync-log-drawer ref="logDrawerRef" />
<order-bill-product-check-drawer ref="productCheckDrawerRef" />
@@ -233,6 +275,78 @@ const exportFieldRef = ref()
const logDrawerRef = ref()
const productCheckDrawerRef = ref()
const checkLoading = ref(false)
const exportLoading = ref(false)
const exportFinished = ref(false)
const exportProgressVisible = ref(false)
const exportProgressPercent = ref(0)
const exportProgressMessage = ref('')
const exportProgressStatus = ref(undefined)
const exportProgressRowCount = ref(0)
const exportTaskId = ref('')
let exportFloatHideTimer = null
const exportFloatVisible = computed(() => exportLoading.value || exportFinished.value)
const exportProgressDegree = computed(() => Math.round(((exportProgressPercent.value || 0) / 100) * 360))
const exportProgressText = computed(() => (exportProgressPercent.value || 0) + '%')
const exportDialogTitle = computed(() => {
if (exportLoading.value) return '异步导出进行中'
if (exportFinished.value) return '异步导出完成'
return '异步导出'
})
const exportFloatTitle = computed(() => {
if (exportLoading.value) return '导出中'
if (exportProgressStatus.value === 'exception') return '导出失败'
if (exportFinished.value) return '导出完成'
return '导出'
})
const exportFloatSummary = computed(() => {
const pct = exportProgressPercent.value || 0
const rows = exportProgressRowCount.value || 0
if (rows > 0) return pct + '% / ' + rows + ' 行'
return pct + '%'
})
const exportFloatTooltip = computed(() => exportProgressMessage.value || '点击查看详情')
function openExportProgress() {
exportProgressVisible.value = true
}
function scheduleHideExportFloat() {
if (exportFloatHideTimer) {
clearTimeout(exportFloatHideTimer)
exportFloatHideTimer = null
}
exportFloatHideTimer = window.setTimeout(() => {
if (!exportLoading.value && exportFinished.value) {
exportFinished.value = false
}
exportFloatHideTimer = null
}, 8000)
}
function applyExportProgress(t) {
const pct = Math.min(100, Number(t && t.percent) || 0)
exportProgressPercent.value = pct
exportProgressRowCount.value = Number(t && t.rowCount) || 0
const st = String((t && t.status) || '').toUpperCase()
if (st === 'PENDING') {
exportProgressMessage.value = (t && t.message) || '排队中...'
exportProgressStatus.value = undefined
} else if (st === 'RUNNING') {
exportProgressMessage.value = (t && t.message) || ('导出中 ' + pct + '%')
exportProgressStatus.value = undefined
} else if (st === 'SUCCESS') {
exportProgressMessage.value = (t && t.message) || '导出完成,正在下载'
exportProgressStatus.value = 'success'
exportProgressPercent.value = 100
} else if (st === 'FAIL' || st === 'FAILED') {
exportProgressMessage.value = (t && (t.error || t.message)) || '导出失败'
exportProgressStatus.value = 'exception'
} else {
exportProgressMessage.value = (t && t.message) || ''
}
}
const batchLoading = ref(false)
const rebillFinished = ref(false)
const rebillProgressVisible = ref(false)
@@ -516,21 +630,67 @@ function queryList(params) {
}
async function handleExport() {
if (exportLoading.value) return
const params = tableRef.value?.getQueryParams?.() || {}
if (!validateOrderIdentifierLimit(params.orderIdentifiers)) return
const fields = await exportFieldRef.value?.open?.()
if (!fields || !fields.length) return
return exportAsyncFromTable({
baseUrl: '/price/billing/orderBill/export',
tableRef: tableRef.value,
normalize: (raw) => {
const q = applySearchRanges(raw)
q.exportFields = fields
return q
},
filePrefix: 'order_bill',
requireFilter: true
})
if (exportFloatHideTimer) {
clearTimeout(exportFloatHideTimer)
exportFloatHideTimer = null
}
exportLoading.value = true
exportFinished.value = false
exportProgressVisible.value = false
exportProgressPercent.value = 0
exportProgressRowCount.value = 0
exportProgressStatus.value = undefined
exportProgressMessage.value = '导出任务已提交'
exportTaskId.value = ''
try {
const result = await exportAsyncFromTable({
baseUrl: '/price/billing/orderBill/export',
tableRef: tableRef.value,
normalize: (raw) => {
const q = applySearchRanges(raw)
q.exportFields = fields
return q
},
filePrefix: 'order_bill',
requireFilter: true,
showLoading: false,
showSuccessMessage: true,
showErrorMessage: true,
onTaskId: (id) => { exportTaskId.value = id },
onProgress: (task) => applyExportProgress(task || {})
})
if (result && result.task) {
applyExportProgress({ ...(result.task || {}), status: 'SUCCESS', percent: 100 })
exportFinished.value = true
scheduleHideExportFloat()
} else if (!exportTaskId.value) {
// filter rejected / cancelled before task created
exportFinished.value = false
exportProgressPercent.value = 0
exportProgressMessage.value = ''
exportProgressStatus.value = undefined
} else if (exportProgressStatus.value !== 'exception') {
exportProgressStatus.value = 'exception'
exportProgressMessage.value = exportProgressMessage.value || '导出失败'
exportFinished.value = true
scheduleHideExportFloat()
} else {
exportFinished.value = true
scheduleHideExportFloat()
}
} catch (e) {
exportProgressStatus.value = 'exception'
exportProgressMessage.value = (e && e.message) || '导出失败'
exportFinished.value = true
scheduleHideExportFloat()
} finally {
exportLoading.value = false
}
}
@@ -889,6 +1049,12 @@ onBeforeUnmount(() => {
.rebill-progress__msg { margin-top: 12px; color: var(--el-text-color-regular); }
.rebill-progress__text { margin-top: 8px; font-size: 13px; color: var(--el-text-color-secondary); }
.rebill-progress__hint { margin-top: 12px; font-size: 12px; color: var(--el-color-warning); }
.order-bill-query .export-status-float {
top: 92px;
}
.order-bill-query .export-status-float.is-offset {
top: 178px;
}
.order-bill-query .retry-status-float {
position: fixed;
top: 92px;