✨ 完善毛利分析筛选与导出日志

This commit is contained in:
caojia
2026-10-15 14:37:18 +08:00
parent be2a359698
commit c704021f2f
15 changed files with 534 additions and 39 deletions
+32 -3
View File
@@ -1,4 +1,15 @@
import request, { download } from '@/utils/request'
import request from '@/utils/request'
import { tansParams } from '@/utils/bms'
function createProfitExport(url, params) {
return request({
url,
method: 'post',
data: params,
transformRequest: [tansParams],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
}
export function listProfitSummary(params) {
return request({
@@ -25,13 +36,30 @@ export function listProfitProducts(params) {
}
export function exportProfitOrders(params) {
return download('/report/profit/orders/export', params, '订单毛利报表.xlsx')
return createProfitExport('/report/profit/orders/export', params)
}
export function exportProfitProducts(params) {
return download('/report/profit/products/export', params, '产品毛利报表.xlsx')
return createProfitExport('/report/profit/products/export', params)
}
export function listProfitExports(params) {
return request({
url: '/report/profit/exports',
method: 'get',
params
})
}
export function downloadProfitExport(exportId) {
return request({
url: '/report/profit/exports/' + exportId + '/download',
method: 'get',
responseType: 'blob'
})
}
/* 提成功能暂停使用,保留接口定义便于恢复。
export function listProfitCommissionRates() {
return request({
url: '/report/profit/commission-rates',
@@ -53,3 +81,4 @@ export function deleteProfitCommissionRate(rateId) {
method: 'delete'
})
}
*/
+144 -7
View File
@@ -65,6 +65,15 @@
/>
</el-form-item>
</el-col>
<el-col v-if="dimension === 'order'" :xs="24" :sm="12" :lg="8">
<el-form-item label="应收状态" class="filter-item">
<el-select v-model="arBillStatus" clearable placeholder="全部状态" class="filter-control">
<el-option label="未出账" value="UNBILLED" />
<el-option label="部分出账" value="PARTIAL" />
<el-option label="已出账" value="BILLED" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :lg="8" class="filter-actions-col">
<div class="filter-item filter-actions">
<el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
@@ -97,15 +106,19 @@
<el-col :xs="12" :sm="8" :md="6">
<div class="metric-item"><div class="metric-label">销售毛利</div><div class="metric-value profit-value">{{ money(summary.salesGrossProfit) }} <small>{{ rate(summary.salesGrossMarginRate) }}</small></div></div>
</el-col>
<!-- 提成功能暂停使用,保留代码便于恢复。
<el-col :xs="12" :sm="8" :md="6">
<div class="metric-item"><div class="metric-label">提成金额</div><div class="metric-value">{{ money(summary.commissionAmount) }}</div></div>
</el-col>
-->
<el-col :xs="12" :sm="8" :md="6">
<div class="metric-item"><div class="metric-label">价格缺失订单</div><div class="metric-value">{{ summary.incompleteOrderCount || 0 }}</div></div>
</el-col>
<!-- 提成功能暂停使用。
<el-col :xs="12" :sm="8" :md="6">
<div class="metric-item"><div class="metric-label">提成未计算订单</div><div class="metric-value">{{ summary.commissionMissingOrderCount || 0 }}</div></div>
</el-col>
-->
</el-row>
</el-card>
</div>
@@ -117,11 +130,18 @@
<el-tab-pane label="订单维度" name="order" />
</el-tabs>
<div class="table-toolbar">
<!-- 提成功能暂停使用。
<el-button
icon="Setting"
v-hasPermi="['report:profit:commission:list']"
@click="openCommissionRates"
>提成比例设置</el-button>
-->
<el-button
v-hasPermi="['report:profit:export']"
icon="Tickets"
@click="openExportLogs"
>导出日志</el-button>
<el-button
type="primary"
plain
@@ -151,10 +171,12 @@
<el-table-column prop="salesAmount" label="销售价" min-width="130" align="right"><template #default="{ row }">{{ money(row.salesAmount) }}</template></el-table-column>
<el-table-column prop="salesGrossProfit" label="销售毛利" min-width="130" align="right"><template #default="{ row }">{{ money(row.salesGrossProfit) }}</template></el-table-column>
<el-table-column prop="salesGrossMarginRate" label="销售毛利率" min-width="120" align="right"><template #default="{ row }">{{ rate(row.salesGrossMarginRate) }}</template></el-table-column>
<!-- 提成功能暂停使用。
<el-table-column prop="commissionAmount" label="提成金额" min-width="120" align="right"><template #default="{ row }">{{ money(row.commissionAmount) }}</template></el-table-column>
<el-table-column prop="commissionMissingOrderCount" label="提成未计算订单" width="150" align="right">
<template #default="{ row }"><span class="success-value order-count-link" :class="{ 'is-disabled': !row.commissionMissingOrderCount }" @click.stop="openOrderList(row, 'COMMISSION_MISSING', '提成未计算订单')">{{ row.commissionMissingOrderCount }}</span></template>
</el-table-column>
-->
<el-table-column prop="incompleteOrderCount" label="价格缺失订单" width="130" align="right">
<template #default="{ row }"><span class="success-value order-count-link" :class="{ 'is-disabled': !row.incompleteOrderCount }" @click.stop="openOrderList(row, 'INCOMPLETE', '价格缺失订单')">{{ row.incompleteOrderCount }}</span></template>
</el-table-column>
@@ -177,8 +199,10 @@
<el-table-column prop="salesAmount" label="销售价" min-width="130" align="right"><template #default="{ row }">{{ money(row.salesAmount) }}</template></el-table-column>
<el-table-column prop="salesGrossProfit" label="销售毛利" min-width="130" align="right"><template #default="{ row }">{{ money(row.salesGrossProfit) }}</template></el-table-column>
<el-table-column prop="salesGrossMarginRate" label="销售毛利率" min-width="120" align="right"><template #default="{ row }">{{ rate(row.salesGrossMarginRate) }}</template></el-table-column>
<!-- 提成功能暂停使用。
<el-table-column prop="commissionRate" label="提成比例" width="110" align="right"><template #default="{ row }">{{ commissionRateFormat(row.commissionRate) }}</template></el-table-column>
<el-table-column prop="commissionAmount" label="提成金额" min-width="120" align="right"><template #default="{ row }">{{ money(row.commissionAmount) }}</template></el-table-column>
-->
<el-table-column prop="arBillStatus" label="应收状态" width="110" align="center">
<template #default="{ row }">{{ arBillStatusLabel(row.arBillStatus) }}</template>
</el-table-column>
@@ -223,6 +247,37 @@
<template #footer><el-button @click="orderListDialogVisible = false">关闭</el-button></template>
</el-dialog>
<el-dialog v-model="exportLogVisible" title="毛利分析导出日志" width="1000px" append-to-body>
<el-table v-loading="exportLogLoading" :data="exportLogs" border stripe>
<el-table-column prop="fileName" label="文件名" min-width="280" show-overflow-tooltip />
<el-table-column prop="dimension" label="导出维度" width="120" align="center">
<template #default="{ row }">{{ exportDimensionLabel(row.dimension) }}</template>
</el-table-column>
<el-table-column prop="rowCount" label="数据条数" width="100" align="right" />
<el-table-column prop="exportStatus" label="状态" width="100" align="center">
<template #default="{ row }"><el-tag :type="exportStatusType(row.exportStatus)">{{ exportStatusLabel(row.exportStatus) }}</el-tag></template>
</el-table-column>
<el-table-column prop="remark" label="失败原因" min-width="180" show-overflow-tooltip />
<el-table-column prop="exportBy" label="导出人" width="120" />
<el-table-column prop="exportTime" label="导出时间" width="170" align="center"><template #default="{ row }">{{ parseTime(row.exportTime) }}</template></el-table-column>
<el-table-column label="操作" width="90" fixed="right" align="center">
<template #default="{ row }">
<el-button v-if="row.exportStatus === 'SUCCESS'" link type="primary" @click="handleDownloadExport(row)">下载</el-button>
</template>
</el-table-column>
<template #empty><el-empty description="暂无导出记录" /></template>
</el-table>
<pagination
v-show="exportLogTotal > 0"
v-model:page="exportLogPageNum"
v-model:limit="exportLogPageSize"
:total="exportLogTotal"
@pagination="loadExportLogs"
/>
<template #footer><el-button @click="exportLogVisible = false">关闭</el-button></template>
</el-dialog>
<!-- 提成设置与编辑弹窗暂停使用。
<el-dialog v-model="commissionDialogVisible" title="业务员固定提成比例" width="760px" append-to-body>
<div class="commission-toolbar">
<span class="commission-tip">按业务员编码配置,比例输入百分数,例如 5 表示 5%。</span>
@@ -255,17 +310,22 @@
<el-button @click="rateFormVisible = false">取消</el-button>
</template>
</el-dialog>
-->
</div>
</template>
<script setup name="ProfitReport">
import { deleteProfitCommissionRate, exportProfitOrders, exportProfitProducts, listProfitCommissionRates, listProfitOrders, listProfitProducts, listProfitSummary, saveProfitCommissionRate } from '@/api/report/profit'
import { downloadProfitExport, exportProfitOrders, exportProfitProducts, listProfitExports, listProfitOrders, listProfitProducts, listProfitSummary } from '@/api/report/profit'
// 提成功能暂停使用:import { deleteProfitCommissionRate, listProfitCommissionRates, saveProfitCommissionRate } from '@/api/report/profit'
import { parseTime } from '@/utils/bms'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ElMessage } from 'element-plus'
import { saveAs } from 'file-saver'
// 提成功能暂停使用:import { ElMessageBox } from 'element-plus'
const dimension = ref('product')
const multiValueFilterLimit = 2000
const dateRange = ref([formatLocalDate(-30), formatLocalDate(0)])
const arBillStatus = ref('')
const query = reactive({
orderNo: '',
customerCode: '',
@@ -278,6 +338,12 @@ const rows = ref([])
let rowsRequestVersion = 0
const loading = ref(false)
const exporting = ref(false)
const exportLogVisible = ref(false)
const exportLogLoading = ref(false)
const exportLogs = ref([])
const exportLogPageNum = ref(1)
const exportLogPageSize = ref(10)
const exportLogTotal = ref(0)
const pageNum = ref(1)
const pageSize = ref(10)
const total = ref(0)
@@ -290,6 +356,7 @@ const orderListPageNum = ref(1)
const orderListPageSize = ref(10)
const orderListContext = ref(null)
let orderListRequestVersion = 0
/* 提成表单状态与初始化暂停使用,保留代码便于恢复。
const commissionDialogVisible = ref(false)
const commissionLoading = ref(false)
const commissionRates = ref([])
@@ -313,6 +380,7 @@ function createRateForm(row = {}) {
remark: row.remark || ''
}
}
*/
function formatLocalDate(offset) {
const date = new Date()
@@ -323,8 +391,9 @@ function formatLocalDate(offset) {
return `${year}-${month}-${day}`
}
function buildQuery(paged = true) {
function buildQuery(paged = true, includeArBillStatus = dimension.value === 'order') {
const params = { ...query }
if (includeArBillStatus && arBillStatus.value) params.arBillStatus = arBillStatus.value
if (dateRange.value?.[0]) params.beginBizDate = dateRange.value[0] + ' 00:00:00'
if (dateRange.value?.[1]) params.endBizDate = dateRange.value[1] + ' 23:59:59'
if (paged) {
@@ -380,20 +449,23 @@ function handleSearch() {
function handleReset() {
Object.keys(query).forEach(key => { query[key] = '' })
arBillStatus.value = ''
dateRange.value = [formatLocalDate(-30), formatLocalDate(0)]
handleSearch()
}
function handleDimensionChange() {
if (dimension.value !== 'order') arBillStatus.value = ''
pageNum.value = 1
rows.value = []
total.value = 0
loadRows()
Promise.all([loadSummary(), loadRows()])
}
function openOrderList(row, detailType, label) {
if (!validateMultiValueFilters()) return
const count = detailType === 'ALL' ? row.orderCount : detailType === 'COMMISSION_MISSING' ? row.commissionMissingOrderCount : row.incompleteOrderCount
// 提成未计算订单下钻暂停使用:detailType === 'COMMISSION_MISSING' ? row.commissionMissingOrderCount
const count = detailType === 'ALL' ? row.orderCount : row.incompleteOrderCount
if (!count) return
orderListTitle.value = `${label}订单列表`
orderListContext.value = {
@@ -448,11 +520,13 @@ function rate(value) {
return Number.isFinite(number) ? number.toFixed(2) + '%' : '-'
}
/* 提成比例格式化暂停使用。
function commissionRateFormat(value) {
if (value === null || value === undefined || value === '') return '-'
const number = Number(value)
return Number.isFinite(number) ? number.toFixed(4) + '%' : '-'
}
*/
function arBillStatusLabel(value) {
const labels = {
@@ -463,13 +537,73 @@ function arBillStatusLabel(value) {
return labels[value] || value || '-'
}
function handleExport() {
async function handleExport() {
if (!validateMultiValueFilters()) return
exporting.value = true
const exporter = dimension.value === 'product' ? exportProfitProducts : exportProfitOrders
exporter(buildQuery(false)).finally(() => { exporting.value = false })
try {
const response = await exporter(buildQuery(false))
const exportLog = response.data
const file = await downloadProfitExport(exportLog.exportId)
if (file.type?.includes('json')) {
const error = JSON.parse(await file.text())
throw new Error(error.msg || '下载导出文件失败')
}
saveAs(file, exportLog.fileName)
ElMessage.success('报表已导出,记录已保存到导出日志')
} catch (error) {
ElMessage.error(error?.message || '报表导出失败')
} finally {
exporting.value = false
if (exportLogVisible.value) loadExportLogs()
}
}
function openExportLogs() {
exportLogPageNum.value = 1
exportLogVisible.value = true
loadExportLogs()
}
function loadExportLogs() {
exportLogLoading.value = true
listProfitExports({ pageNum: exportLogPageNum.value, pageSize: exportLogPageSize.value }).then(response => {
exportLogs.value = response.rows || []
exportLogTotal.value = response.total || 0
}).catch(() => {
exportLogs.value = []
exportLogTotal.value = 0
}).finally(() => {
exportLogLoading.value = false
})
}
function exportDimensionLabel(value) {
return value === 'PRODUCT' ? '产品维度' : value === 'ORDER' ? '订单维度' : value || '-'
}
function exportStatusLabel(value) {
return value === 'PROCESSING' ? '生成中' : value === 'SUCCESS' ? '成功' : value === 'FAILED' ? '失败' : value || '-'
}
function exportStatusType(value) {
return value === 'SUCCESS' ? 'success' : value === 'FAILED' ? 'danger' : 'info'
}
async function handleDownloadExport(row) {
try {
const file = await downloadProfitExport(row.exportId)
if (file.type?.includes('json')) {
const error = JSON.parse(await file.text())
throw new Error(error.msg || '下载导出文件失败')
}
saveAs(file, row.fileName)
} catch (error) {
ElMessage.error(error?.message || '下载导出文件失败')
}
}
/* 提成配置交互暂停使用。
function openCommissionRates() {
commissionDialogVisible.value = true
commissionLoading.value = true
@@ -514,6 +648,7 @@ function handleDeleteRate(row) {
}
})
}
*/
handleSearch()
</script>
@@ -661,6 +796,7 @@ handleSearch()
padding-bottom: 8px;
}
/* 提成设置弹窗样式暂停使用。
.commission-toolbar {
display: flex;
align-items: center;
@@ -673,6 +809,7 @@ handleSearch()
color: var(--el-text-color-secondary);
font-size: 13px;
}
*/
@media (max-width: 768px) {
.summary-list {