增加导出功能

This commit is contained in:
wuweihua
2026-10-26 16:20:46 +08:00
parent 7f4fc3df8c
commit 76cc5b5781
16 changed files with 801 additions and 10 deletions
+27
View File
@@ -100,3 +100,30 @@ export function listOrderSourceOrdersByProduct(data) {
})
}
/** 异步导出:提交任务 */
export function exportOrderSource(data) {
return request({
url: '/monitor/orderSource/export',
method: 'post',
data,
timeout: 120000,
headers: { repeatSubmit: false }
})
}
export function getOrderSourceExportTask(taskId) {
return request({
url: '/monitor/orderSource/export/task/' + taskId,
method: 'get'
})
}
export function downloadOrderSourceExport(taskId) {
return request({
url: '/monitor/orderSource/export/download/' + taskId,
method: 'get',
responseType: 'blob',
timeout: 120000
})
}
@@ -0,0 +1,137 @@
<template>
<el-dialog
v-model="visible"
:title="title"
width="560px"
append-to-body
destroy-on-close
@closed="onClosed"
>
<div class="export-field-toolbar">
<el-checkbox
:model-value="allChecked"
:indeterminate="indeterminate"
@change="toggleAll"
>全选</el-checkbox>
<el-button link type="primary" @click="resetDefault">恢复默认</el-button>
</div>
<el-checkbox-group v-model="checked" class="export-field-grid">
<el-checkbox
v-for="f in fields"
:key="f.prop"
:value="f.prop"
>{{ f.label }}</el-checkbox>
</el-checkbox-group>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :disabled="!checked.length" @click="confirm">确定导出</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
const props = defineProps({
title: { type: String, default: '选择导出字段' },
fields: { type: Array, default: () => [] },
/** localStorage key to remember last selection */
storageKey: { type: String, default: '' }
})
const visible = ref(false)
const checked = ref([])
let resolveFn = null
const allProps = computed(() => (props.fields || []).map(f => f.prop).filter(Boolean))
const allChecked = computed(() => allProps.value.length > 0 && checked.value.length === allProps.value.length)
const indeterminate = computed(() => checked.value.length > 0 && checked.value.length < allProps.value.length)
function defaultChecked() {
return allProps.value.slice()
}
function loadStored() {
if (!props.storageKey) return null
try {
const raw = localStorage.getItem(props.storageKey)
if (!raw) return null
const arr = JSON.parse(raw)
if (!Array.isArray(arr)) return null
const allow = new Set(allProps.value)
return arr.map(String).filter(p => allow.has(p))
} catch (e) {
return null
}
}
function saveStored(list) {
if (!props.storageKey) return
try {
localStorage.setItem(props.storageKey, JSON.stringify(list || []))
} catch (e) { /* ignore */ }
}
function toggleAll(val) {
checked.value = val ? defaultChecked() : []
}
function resetDefault() {
checked.value = defaultChecked()
}
function open() {
const stored = loadStored()
checked.value = (stored && stored.length) ? stored : defaultChecked()
visible.value = true
return new Promise(resolve => {
resolveFn = resolve
})
}
function confirm() {
if (!checked.value.length) {
ElMessage.warning('请至少选择一个导出字段')
return
}
// keep declaration order
const allow = new Set(checked.value)
const ordered = allProps.value.filter(p => allow.has(p))
saveStored(ordered)
visible.value = false
if (resolveFn) {
resolveFn(ordered)
resolveFn = null
}
}
function onClosed() {
if (resolveFn) {
resolveFn(null)
resolveFn = null
}
}
defineExpose({ open })
</script>
<style scoped>
.export-field-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.export-field-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px 12px;
max-height: 360px;
overflow: auto;
}
.export-field-grid :deep(.el-checkbox) {
margin-right: 0;
height: auto;
white-space: normal;
}
</style>
+1 -1
View File
@@ -142,7 +142,7 @@ export async function exportAsync(options = {}) {
const extraIgnore = (options.filterOpts && options.filterOpts.ignoreKeys) || []
const filterOpts = {
...(options.filterOpts || {}),
ignoreKeys: ['orderStatuses', ...extraIgnore]
ignoreKeys: ['orderStatuses', 'exportFields', ...extraIgnore]
}
if (!hasListFilter(query, filterOpts)) {
ElMessage.warning(options.filterMessage || '\u8bf7\u5148\u8bbe\u7f6e\u5217\u8868\u67e5\u8be2\u6761\u4ef6\u540e\u518d\u5bfc\u51fa')
+74
View File
@@ -0,0 +1,74 @@
/** Excel export field options (prop must match backend export-row Java field names). */
export const ORDER_SOURCE_EXPORT_FIELDS = [
{ prop: 'orderId', label: '客户订单号' },
{ prop: 'orgOrderId', label: '运单号' },
{ prop: 'transferOrderId', label: '转单号' },
{ prop: 'trackId', label: '轨迹ID' },
{ prop: 'orderStatusLabel', label: '订单状态' },
{ prop: 'customerCode', label: '客户代码' },
{ prop: 'customerName', label: '客户名称' },
{ prop: 'productCode', label: '产品代码' },
{ prop: 'productName', label: '产品名称' },
{ prop: 'productFloorCode', label: '产品底级代码' },
{ prop: 'country', label: '国家代码' },
{ prop: 'countryName', label: '国家/地区' },
{ prop: 'consigneePostcode', label: '收件人邮编' },
{ prop: 'confirmWeight', label: '客户确认重' },
{ prop: 'dimensionalWeightFactor', label: '计费重' },
{ prop: 'volumeWeight', label: '体积重' },
{ prop: 'weight', label: '实重' },
{ prop: 'pickupDate', label: '提货时间' }
]
export const ORDER_BILL_EXPORT_FIELDS = [
{ prop: 'orderNo', label: '订单号' },
{ prop: 'trackingNo', label: '运单号' },
{ prop: 'transferNo', label: '转单号' },
{ prop: 'customerName', label: '客户' },
{ prop: 'customerCode', label: '客户代码' },
{ prop: 'productCode', label: '产品代码' },
{ prop: 'productName', label: '产品名称' },
{ prop: 'countryCode', label: '国家代码' },
{ prop: 'countryName', label: '国家/地区' },
{ prop: 'pieces', label: '件数' },
{ prop: 'weight', label: '计费重' },
{ prop: 'volumeWeight', label: '体积重' },
{ prop: 'receiveWeight', label: '实重' },
{ prop: 'destPostcode', label: '收件人邮编' },
{ prop: 'orderStatusLabel', label: '订单状态' },
{ prop: 'billingFailStatusLabel', label: '计费状态' },
{ prop: 'bizDate', label: '计费时间' },
{ prop: 'orderTime', label: '下单时间' },
{ prop: 'pickupDate', label: '提货时间' },
{ prop: 'belongSaler', label: '销售员' },
{ prop: 'settlementMan', label: '结算员' },
{ prop: 'serviceMan', label: '客服员' }
]
export const BILLING_BILL_EXPORT_FIELDS = [
{ prop: 'orderNo', label: '订单号' },
{ prop: 'trackingNo', label: '运单号' },
{ prop: 'transferNo', label: '转单号' },
{ prop: 'customerName', label: '客户' },
{ prop: 'customerCode', label: '客户代码' },
{ prop: 'productCode', label: '产品代码' },
{ prop: 'productName', label: '产品名称' },
{ prop: 'countryCode', label: '国家代码' },
{ prop: 'countryName', label: '国家/地区' },
{ prop: 'pieces', label: '件数' },
{ prop: 'chargeWeight', label: '计费重' },
{ prop: 'weight', label: '原计费重' },
{ prop: 'receiveWeight', label: '收货重' },
{ prop: 'incomeTotal', label: '应收总额' },
{ prop: 'payTotal', label: '应付总额' },
{ prop: 'matchSourceLabel', label: '计价来源' },
{ prop: 'billingFailStatusLabel', label: '计费状态' },
{ prop: 'customerActiveStatusLabel', label: '客户状态' },
{ prop: 'creditArrearsStatusLabel', label: '额度欠费' },
{ prop: 'bizDate', label: '计费时间' },
{ prop: 'orderTime', label: '下单时间' },
{ prop: 'belongSaler', label: '销售员' },
{ prop: 'settlementMan', label: '结算员' },
{ prop: 'serviceMan', label: '客服员' }
]
@@ -65,6 +65,15 @@
@click="logDrawerRef.open()"
>操作记录</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
v-hasPermi="['monitor:orderSource:export']"
@click="handleExport"
>导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
@@ -131,10 +140,19 @@
<order-source-sync-log-drawer ref="logDrawerRef" />
<order-source-product-check-drawer ref="productCheckDrawerRef" />
<order-source-compare-drawer ref="compareDrawerRef" @fill-search="onCompareFillSearch" />
<excel-export-field-dialog
ref="exportFieldRef"
:fields="ORDER_SOURCE_EXPORT_FIELDS"
storage-key="exportFields:orderSource"
/>
</div>
</template>
<script setup name="OrderSource">
import { exportAsync } from '@/utils/excelExport'
import ExcelExportFieldDialog from '@/components/ExcelExportFieldDialog/index.vue'
import { ORDER_SOURCE_EXPORT_FIELDS } from '@/utils/exportFieldDefs'
import { ElMessage } from 'element-plus'
import { listOrderSource, listOrderSourceByTransferNos, syncOrderSourceSelected } from '@/api/monitor/orderSource'
import OrderSourceSyncDialog from './components/OrderSourceSyncDialog.vue'
@@ -144,6 +162,7 @@ import OrderSourceCompareDrawer from './components/OrderSourceCompareDrawer.vue'
const { proxy } = getCurrentInstance()
const tableRef = ref()
const exportFieldRef = ref()
const syncDialogRef = ref()
const logDrawerRef = ref()
const productCheckDrawerRef = ref()
@@ -330,6 +349,32 @@ function buildFilterQuery(params) {
return query
}
async function handleExport() {
const q = buildFilterQuery()
const identifierCount = (q.orderIdentifiers || []).length
const hasIdentifier = identifierCount > 0
const hasCustomer = !!(q.customerKeywords && q.customerKeywords.length) || !!(q.customerCodes && q.customerCodes.length)
const hasTime = !!q.beginTime && !!q.endTime
if (!hasIdentifier && !(hasCustomer && hasTime)) {
ElMessage.warning('请先设置列表查询条件后再导出')
return
}
if (identifierCount > MAX_ORDER_IDENTIFIERS) {
ElMessage.warning(`单次最多导出 ${MAX_ORDER_IDENTIFIERS} 个单号,当前 ${identifierCount} 个`)
return
}
const fields = await exportFieldRef.value?.open?.()
if (!fields || !fields.length) return
return exportAsync({
baseUrl: '/monitor/orderSource/export',
query: { ...q, exportFields: fields },
requireFilter: false,
filePrefix: 'order_source'
})
}
function queryList(params) {
const src = params || tableRef.value?.getQueryParams?.() || {}
const identifiers = splitLines(src.orderIdentifiers)
+18 -2
View File
@@ -234,12 +234,20 @@
<div class="import-progress-msg">{{ cwImportProgressMessage }}</div>
</div>
</el-dialog>
<excel-export-field-dialog
ref="exportFieldRef"
:fields="BILLING_BILL_EXPORT_FIELDS"
storage-key="exportFields:billingBill"
/>
</div>
</template>
<script setup name="BillingBill">
import { listBill, getBill, refundBill, updateCreditArrears, importChargeWeightAsync, downloadChargeWeightTemplate, recalcBillPriceAsync } from '@/api/price/billing'
import { exportAsyncFromTable } from '@/utils/excelExport'
import ExcelExportFieldDialog from '@/components/ExcelExportFieldDialog/index.vue'
import { BILLING_BILL_EXPORT_FIELDS } from '@/utils/exportFieldDefs'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useProducts } from '@/components/ProductSelect/useProducts'
import BillDetail from './components/BillDetail.vue'
@@ -281,6 +289,7 @@ const productOptions = computed(() =>
)
const tableRef = ref()
const exportFieldRef = ref()
const tableHeight = ref(480)
let tableResizeObserver = null
@@ -442,18 +451,25 @@ function queryList(params) {
return listBill(query)
}
function handleExport() {
async function handleExport() {
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: buildBillQuery,
normalize: (raw) => {
const q = buildBillQuery(raw)
q.exportFields = fields
return q
},
filePrefix: 'billing_bill',
requireFilter: true
})
}
function handleUpdateArrears(ids, selection) {
const billIds = (ids && ids.length)
? ids
+17 -3
View File
@@ -188,6 +188,12 @@
</el-dialog>
<order-source-sync-log-drawer ref="logDrawerRef" />
<order-bill-product-check-drawer ref="productCheckDrawerRef" />
<excel-export-field-dialog
ref="exportFieldRef"
:fields="ORDER_BILL_EXPORT_FIELDS"
storage-key="exportFields:orderBill"
/>
</div>
</template>
@@ -195,10 +201,13 @@
import { ElMessage, ElMessageBox } from 'element-plus'
import { listOrderBill, localRebillAsync } from '@/api/price/billing'
import { exportAsyncFromTable } from '@/utils/excelExport'
import ExcelExportFieldDialog from '@/components/ExcelExportFieldDialog/index.vue'
import { ORDER_BILL_EXPORT_FIELDS } from '@/utils/exportFieldDefs'
import OrderSourceSyncLogDrawer from '../../monitor/orderSource/components/OrderSourceSyncLogDrawer.vue'
import OrderBillProductCheckDrawer from './components/OrderBillProductCheckDrawer.vue'
const tableRef = ref()
const exportFieldRef = ref()
const logDrawerRef = ref()
const productCheckDrawerRef = ref()
const checkLoading = ref(false)
@@ -417,19 +426,24 @@ function queryList(params) {
return listOrderBill(query)
}
function handleExport() {
async function handleExport() {
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: applySearchRanges,
normalize: (raw) => {
const q = applySearchRanges(raw)
q.exportFields = fields
return q
},
filePrefix: 'order_bill',
requireFilter: true
})
}
const MAX_REBILL = 10000
function currentIdentifiers() {
const q = tableRef.value?.getQueryParams?.() || {}