✨ 按客户区域计算毛利并完善汇总展示
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user