✨ 按客户区域计算毛利并完善汇总展示

This commit is contained in:
caojia
2026-11-05 15:41:20 +08:00
parent 891fdea7d3
commit 25801fe42f
9 changed files with 248 additions and 77 deletions
+4
View File
@@ -19,6 +19,10 @@ export function listProfitCustomerOptions(params) {
})
}
export function listProfitAreaRules() {
return request({ url: '/report/profit/area-rules', method: 'get' })
}
export function listProfitSummary(params) {
return request({
url: '/report/profit/summary',
+53 -22
View File
@@ -70,7 +70,7 @@
</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" :disabled="!hasCustomerArea()" @click="handleSearch">搜索</el-button>
<el-button type="primary" icon="Search" :disabled="!hasProfitRule()" @click="handleSearch">搜索</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</div>
</el-col>
@@ -78,23 +78,26 @@
</el-form>
</el-card>
<div v-if="summaryList.length" class="summary-list">
<el-card v-for="summary in summaryList" :key="summary.currency" shadow="never" class="summary-card">
<el-alert v-if="areaRulesFailed" title="毛利规则加载失败,请刷新页面重试" type="error" :closable="false" show-icon class="rule-alert" />
<el-alert v-else-if="areaRules && hasCustomerArea() && !currentRule" title="该客户区域未配置毛利规则" type="warning" :closable="false" show-icon class="rule-alert" />
<div v-if="summaryLoaded && hasProfitRule()" class="summary-list">
<el-card v-for="summary in summaryCards" :key="summary.currency || 'empty'" shadow="never" class="summary-card">
<template #header>
<span>{{ summary.currency || '未标币种' }} 汇总</span>
<span class="summary-subtitle">订单 {{ summary.orderCount || 0 }} 条</span>
<span>{{ summary.empty ? '汇总' : `${summary.currency || '未标币种'} 汇总` }}</span>
<span class="summary-subtitle">订单 {{ summary.orderCount ?? '-' }} 条</span>
</template>
<el-row :gutter="12" class="summary-grid">
<el-col :xs="12" :sm="8" :md="6">
<div class="metric-item"><div class="metric-label">红线价</div><div class="metric-value">{{ money(summary.redlineAmount) }}</div></div>
</el-col>
<el-col :xs="12" :sm="8" :md="6">
<el-col v-if="!isGuangzhou" :xs="12" :sm="8" :md="6">
<div class="metric-item"><div class="metric-label">产品价</div><div class="metric-value">{{ money(summary.costAmount) }}</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.salesAmount) }}</div></div>
</el-col>
<el-col :xs="12" :sm="8" :md="6">
<el-col v-if="!isGuangzhou" :xs="12" :sm="8" :md="6">
<div class="metric-item"><div class="metric-label">红线毛利</div><div class="metric-value">{{ money(summary.redlineGrossProfit) }} <small>{{ rate(summary.redlineGrossMarginRate) }}</small></div></div>
</el-col>
<el-col :xs="12" :sm="8" :md="6">
@@ -139,14 +142,14 @@
icon="Download"
v-hasPermi="['report:profit:export']"
:loading="exporting"
:disabled="!hasCustomerArea()"
:disabled="!hasProfitRule()"
@click="handleExport"
>导出当前维度</el-button>
</div>
</div>
<div v-text-drag-copy="true" class="report-table-copy-area">
<el-table :key="dimension" v-loading="loading" :data="rows" border stripe :row-key="rowKey">
<el-table :key="dimension + '-' + currentRule" v-loading="loading" :data="rows" border stripe :row-key="rowKey">
<template v-if="dimension === 'product'">
<el-table-column prop="productCode" label="产品代码" min-width="120" show-overflow-tooltip />
<el-table-column prop="productName" label="产品名称" min-width="180" show-overflow-tooltip />
@@ -156,10 +159,10 @@
<el-table-column prop="orderCount" label="订单数" width="100" align="right">
<template #default="{ row }"><span class="success-value order-count-link" :class="{ 'is-disabled': !row.orderCount }" @click.stop="openOrderList(row)">{{ row.orderCount }}</span></template>
</el-table-column>
<el-table-column prop="costAmount" label="产品价" min-width="130" align="right"><template #default="{ row }">{{ money(row.costAmount) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="costAmount" label="产品价" min-width="130" align="right"><template #default="{ row }">{{ money(row.costAmount) }}</template></el-table-column>
<el-table-column prop="redlineAmount" label="红线价" min-width="130" align="right"><template #default="{ row }">{{ money(row.redlineAmount) }}</template></el-table-column>
<el-table-column prop="redlineGrossProfit" label="红线毛利" min-width="130" align="right"><template #default="{ row }">{{ money(row.redlineGrossProfit) }}</template></el-table-column>
<el-table-column prop="redlineGrossMarginRate" label="红线毛利率" min-width="120" align="right"><template #default="{ row }">{{ rate(row.redlineGrossMarginRate) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="redlineGrossProfit" label="红线毛利" min-width="130" align="right"><template #default="{ row }">{{ money(row.redlineGrossProfit) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="redlineGrossMarginRate" label="红线毛利率" min-width="120" align="right"><template #default="{ row }">{{ rate(row.redlineGrossMarginRate) }}</template></el-table-column>
<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>
@@ -181,10 +184,10 @@
<el-table-column prop="productName" label="产品名称" min-width="160" show-overflow-tooltip />
<el-table-column prop="countryName" label="国家/地区" min-width="110" show-overflow-tooltip />
<el-table-column prop="currency" label="币种" width="80" align="center" />
<el-table-column prop="costAmount" label="产品价" min-width="130" align="right"><template #default="{ row }">{{ money(row.costAmount) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="costAmount" label="产品价" min-width="130" align="right"><template #default="{ row }">{{ money(row.costAmount) }}</template></el-table-column>
<el-table-column prop="redlineAmount" label="红线价" min-width="130" align="right"><template #default="{ row }">{{ money(row.redlineAmount) }}</template></el-table-column>
<el-table-column prop="redlineGrossProfit" label="红线毛利" min-width="130" align="right"><template #default="{ row }">{{ money(row.redlineGrossProfit) }}</template></el-table-column>
<el-table-column prop="redlineGrossMarginRate" label="红线毛利率" min-width="120" align="right"><template #default="{ row }">{{ rate(row.redlineGrossMarginRate) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="redlineGrossProfit" label="红线毛利" min-width="130" align="right"><template #default="{ row }">{{ money(row.redlineGrossProfit) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="redlineGrossMarginRate" label="红线毛利率" min-width="120" align="right"><template #default="{ row }">{{ rate(row.redlineGrossMarginRate) }}</template></el-table-column>
<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>
@@ -220,9 +223,11 @@
<el-table-column prop="productName" label="产品名称" min-width="160" show-overflow-tooltip />
<el-table-column prop="countryName" label="国家/地区" width="110" />
<el-table-column prop="currency" label="币种" width="80" align="center" />
<el-table-column prop="costAmount" label="产品价" width="110" align="right"><template #default="{ row }">{{ money(row.costAmount) }}</template></el-table-column>
<el-table-column v-if="!isGuangzhou" prop="costAmount" label="产品价" width="110" align="right"><template #default="{ row }">{{ money(row.costAmount) }}</template></el-table-column>
<el-table-column prop="redlineAmount" label="红线价" width="110" align="right"><template #default="{ row }">{{ money(row.redlineAmount) }}</template></el-table-column>
<el-table-column prop="salesAmount" label="销售价" width="110" align="right"><template #default="{ row }">{{ money(row.salesAmount) }}</template></el-table-column>
<el-table-column v-if="isGuangzhou" prop="salesGrossProfit" label="销售毛利" width="110" align="right"><template #default="{ row }">{{ money(row.salesGrossProfit) }}</template></el-table-column>
<el-table-column v-if="isGuangzhou" prop="salesGrossMarginRate" label="销售毛利率" width="110" align="right"><template #default="{ row }">{{ rate(row.salesGrossMarginRate) }}</template></el-table-column>
<el-table-column prop="arBillStatus" label="应收状态" width="100" align="center"><template #default="{ row }">{{ arBillStatusLabel(row.arBillStatus) }}</template></el-table-column>
<template #empty><el-empty description="暂无符合条件的订单" /></template>
</el-table>
@@ -304,7 +309,7 @@
</template>
<script setup name="ProfitReport">
import { downloadProfitExport, exportProfitOrders, exportProfitProducts, listProfitCustomerOptions, listProfitExports, listProfitOrders, listProfitProducts, listProfitSummary } from '@/api/report/profit'
import { downloadProfitExport, exportProfitOrders, exportProfitProducts, listProfitAreaRules, listProfitCustomerOptions, listProfitExports, listProfitOrders, listProfitProducts, listProfitSummary } from '@/api/report/profit'
// 提成功能暂停使用:import { deleteProfitCommissionRate, listProfitCommissionRates, saveProfitCommissionRate } from '@/api/report/profit'
import { parseTime } from '@/utils/bms'
import { ElMessage } from 'element-plus'
@@ -323,7 +328,13 @@ const query = reactive({
belongSalerCode: '',
productCode: ''
})
const areaRules = ref(null)
const areaRulesFailed = ref(false)
const currentRule = computed(() => areaRules.value?.[String(query.customerArea)] || '')
const isGuangzhou = computed(() => currentRule.value === 'GUANGZHOU')
const summaryList = ref([])
const summaryLoaded = ref(false)
const summaryCards = computed(() => summaryList.value.length ? summaryList.value : [{ empty: true }])
let summaryRequestVersion = 0
const rows = ref([])
let rowsRequestVersion = 0
@@ -412,17 +423,23 @@ function hasCustomerArea() {
return query.customerArea !== '' && query.customerArea !== null && query.customerArea !== undefined
}
function hasProfitRule() {
return hasCustomerArea() && !!currentRule.value
}
function loadSummary() {
if (!hasCustomerArea() || !validateMultiValueFilters(false)) return Promise.resolve()
if (!hasProfitRule() || !validateMultiValueFilters(false)) return Promise.resolve()
const requestVersion = ++summaryRequestVersion
summaryLoaded.value = false
return listProfitSummary(buildQuery(false)).then(res => {
if (requestVersion !== summaryRequestVersion) return
summaryList.value = res.data || []
summaryLoaded.value = true
})
}
function loadRows() {
if (!hasCustomerArea() || !validateMultiValueFilters()) return Promise.resolve()
if (!hasProfitRule() || !validateMultiValueFilters()) return Promise.resolve()
const requestVersion = ++rowsRequestVersion
const requestDimension = dimension.value
loading.value = true
@@ -441,6 +458,7 @@ function clearReportResults() {
rowsRequestVersion++
orderListRequestVersion++
summaryList.value = []
summaryLoaded.value = false
rows.value = []
total.value = 0
loading.value = false
@@ -453,7 +471,7 @@ function clearReportResults() {
function handleSearch() {
clearReportResults()
if (!hasCustomerArea() || !validateMultiValueFilters()) return
if (!hasProfitRule() || !validateMultiValueFilters()) return
pageNum.value = 1
Promise.all([loadSummary(), loadRows()])
}
@@ -493,7 +511,7 @@ function openOrderList(row) {
function loadOrderList() {
if (!orderListContext.value) return
if (!hasCustomerArea() || !validateMultiValueFilters()) return
if (!hasProfitRule() || !validateMultiValueFilters()) return
const requestVersion = ++orderListRequestVersion
orderListLoading.value = true
const params = {
@@ -547,7 +565,7 @@ function arBillStatusLabel(value) {
}
async function handleExport() {
if (!hasCustomerArea() || !validateMultiValueFilters()) return
if (!hasProfitRule() || !validateMultiValueFilters()) return
exporting.value = true
const exporter = dimension.value === 'product' ? exportProfitProducts : exportProfitOrders
try {
@@ -659,6 +677,15 @@ function handleDeleteRate(row) {
}
*/
listProfitAreaRules().then(res => {
areaRules.value = res.data || {}
handleSearch()
}).catch(() => {
areaRules.value = {}
areaRulesFailed.value = true
clearReportResults()
})
watch(customer_area, options => {
if (!options?.length) {
query.customerArea = ''
@@ -707,6 +734,10 @@ watch(customer_area, options => {
gap: 8px;
}
.rule-alert {
margin-bottom: 14px;
}
.summary-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(560px, 1fr));