This commit is contained in:
van
2026-04-05 21:34:48 +08:00
parent 5fcf92d2da
commit 5cbcfa9533
3 changed files with 225 additions and 51 deletions

View File

@@ -248,6 +248,15 @@ export function batchMarkRebateReceived() {
})
}
/** 按订单 id 批量重算售价(从型号配置回填)与利润 */
export function recalcProfitBatch(ids) {
return request({
url: '/system/jdorder/tools/recalc-profit',
method: 'post',
data: { ids }
})
}
// 生成录单格式文本Excel可粘贴格式
export function generateExcelText(query) {
return request({

View File

@@ -79,6 +79,16 @@
<span>{{ scope.row.commissionPay ? '¥' + scope.row.commissionPay : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="参考售价(直款)" align="center" prop="sellingPriceDirect" width="120">
<template slot-scope="scope">
<span>{{ scope.row.sellingPriceDirect != null ? '¥' + scope.row.sellingPriceDirect : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="参考售价(闲鱼)" align="center" prop="sellingPriceXianyu" width="120">
<template slot-scope="scope">
<span>{{ scope.row.sellingPriceXianyu != null ? '¥' + scope.row.sellingPriceXianyu : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
<template slot-scope="scope">
<el-button
@@ -114,6 +124,12 @@
<el-form-item label="佣金(支付)" prop="commissionPay">
<el-input-number v-model="form.commissionPay" :precision="2" :step="0.1" :min="0" placeholder="请输入佣金(支付)" style="width: 100%;" />
</el-form-item>
<el-form-item label="参考售价(直款)" prop="sellingPriceDirect">
<el-input-number v-model="form.sellingPriceDirect" :precision="2" :step="1" :min="0" placeholder="F 单直款渠道默认售价" style="width: 100%;" />
</el-form-item>
<el-form-item label="参考售价(闲鱼)" prop="sellingPriceXianyu">
<el-input-number v-model="form.sellingPriceXianyu" :precision="2" :step="1" :min="0" placeholder="F 单闲鱼标价系统会×0.984" style="width: 100%;" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
@@ -197,7 +213,9 @@ export default {
jdUrl: null,
commission: 0,
commissionReceive: 0,
commissionPay: 0
commissionPay: 0,
sellingPriceDirect: undefined,
sellingPriceXianyu: undefined
};
this.resetForm("form");
},

View File

@@ -134,13 +134,23 @@
<el-button v-if="!isMobile" type="info" size="small" icon="el-icon-folder-opened" @click="openRebateUploadRecordDialog" title="查看历史上传的后返表原件并可重新下载">后返上传记录</el-button>
<el-button v-if="!isMobile" type="success" size="small" icon="el-icon-document-copy" @click="handleBatchCopyRebateText" :disabled="selectedRows.length === 0" title="批量复制选中订单的后返录表格式Excel可粘贴">批量复制后返录表</el-button>
<el-button v-if="!isMobile" type="info" size="small" icon="el-icon-document-copy" @click="handleBatchCopySichuanCommerceText" :disabled="selectedRows.length === 0" title="批量复制选中订单的四川商贸录表格式(日期 型号 数量 地址 价格 备注 是否安排 物流)">四川商贸录表</el-button>
<el-button v-if="!isMobile" type="warning" size="small" icon="el-icon-refresh" @click="handleBatchRecalcProfit" :disabled="selectedRows.length === 0" title="清除售价/利润手动锁定并按规则重算(依赖型号配置与当前付款、后返)">批量重算利润</el-button>
</div>
</template>
<!-- 表格区域 -->
<template #table>
<div v-if="!isMobile" class="profit-summary-bar">
<span class="profit-summary-item">统计范围<b>{{ profitSummaryLabel }}</b></span>
<span class="profit-summary-item">后返合计<b>{{ toYuan(profitSummaryRebateTotal) }}</b></span>
<span class="profit-summary-item">利润合计<b>{{ profitSummaryProfitTotal === '' ? '—' : toYuan(profitSummaryProfitTotal) }}</b></span>
</div>
<!-- 移动端卡片列表 -->
<div v-if="isMobile" class="mobile-order-list" v-loading="loading">
<div class="profit-summary-bar mobile-profit-summary">
<div>统计<b>{{ profitSummaryLabel }}</b></div>
<div>后返合计 <b>{{ toYuan(profitSummaryRebateTotal) }}</b> · 利润合计 <b>{{ profitSummaryProfitTotal === '' ? '—' : toYuan(profitSummaryProfitTotal) }}</b></div>
</div>
<div
v-for="row in list"
:key="row.id"
@@ -271,6 +281,40 @@
<span class="field-label">后返金额</span>
<span class="field-value amount">{{ toYuan(row.rebateAmount) }}</span>
</div>
<div class="field-row" v-if="row.distributionMark === 'F'">
<span class="field-label">售价渠道</span>
<span class="field-value">
<el-select v-model="row.sellingPriceType" placeholder="渠道" size="mini" clearable style="width: 120px;" @change="onOrderSellingPriceTypeChange(row)">
<el-option label="直款" value="direct" />
<el-option label="闲鱼" value="xianyu" />
</el-select>
</span>
</div>
<div class="field-row" v-if="row.distributionMark === 'F'">
<span class="field-label">售价</span>
<span class="field-value">
<el-input-number v-model="row.sellingPrice" :min="0" :step="1" :precision="2" size="mini" controls-position="right" style="width: 130px;" @change="onOrderSellingPriceChange(row)" />
<el-tag v-if="row.sellingPriceManual === 1" type="info" size="mini" style="margin-left: 6px;">手填</el-tag>
</span>
</div>
<div class="field-row" v-if="row.distributionMark === 'F' || row.distributionMark === 'H-TF'">
<span class="field-label">利润</span>
<span class="field-value">
<el-input-number v-model="row.profit" :step="1" :precision="2" size="mini" controls-position="right" style="width: 130px;" @change="onOrderProfitChange(row)" />
<el-tag v-if="row.profitManual === 1" type="warning" size="mini" style="margin-left: 6px;">手填</el-tag>
</span>
</div>
<div class="field-row" v-else-if="row.profit != null && row.profit !== ''">
<span class="field-label">利润</span>
<span class="field-value amount">{{ toYuan(row.profit) }}</span>
</div>
<div class="field-row" v-if="row.distributionMark === 'F' || row.distributionMark === 'H-TF'">
<span class="field-label">快捷</span>
<span class="field-value">
<el-button v-if="row.distributionMark === 'F'" type="text" size="mini" @click="fillSellingPriceFromConfig(row)">按型号填价</el-button>
<el-button type="text" size="mini" @click="recalcOrderProfitOnly(row)">重算利润</el-button>
</span>
</div>
<div class="field-row" v-if="row.rebateRemarkJson">
<span class="field-label">后返备注</span>
<span class="field-value">
@@ -390,6 +434,45 @@
<el-table-column label="后返金额" prop="rebateAmount" width="110" align="right">
<template slot-scope="scope">{{ toYuan(scope.row.rebateAmount) }}</template>
</el-table-column>
<el-table-column label="售价渠道" prop="sellingPriceType" width="108" align="center">
<template slot-scope="scope">
<template v-if="scope.row.distributionMark === 'F'">
<el-select v-model="scope.row.sellingPriceType" placeholder="—" size="mini" clearable style="width: 100px;" @change="onOrderSellingPriceTypeChange(scope.row)">
<el-option label="直款" value="direct" />
<el-option label="闲鱼" value="xianyu" />
</el-select>
</template>
<span v-else style="color: #c0c4cc;"></span>
</template>
</el-table-column>
<el-table-column label="售价" prop="sellingPrice" width="128" align="right">
<template slot-scope="scope">
<template v-if="scope.row.distributionMark === 'F'">
<el-input-number v-model="scope.row.sellingPrice" :min="0" :step="1" :precision="2" size="mini" controls-position="right" style="width: 118px;" @change="onOrderSellingPriceChange(scope.row)" />
</template>
<span v-else>{{ scope.row.sellingPrice != null ? toYuan(scope.row.sellingPrice) : '' }}</span>
</template>
</el-table-column>
<el-table-column label="利润" prop="profit" width="128" align="right">
<template slot-scope="scope">
<template v-if="scope.row.distributionMark === 'F' || scope.row.distributionMark === 'H-TF'">
<el-input-number v-model="scope.row.profit" :step="1" :precision="2" size="mini" controls-position="right" style="width: 118px;" @change="onOrderProfitChange(scope.row)" />
</template>
<span v-else>{{ scope.row.profit != null ? toYuan(scope.row.profit) : '' }}</span>
</template>
</el-table-column>
<el-table-column label="售价/利润" width="108" align="center">
<template slot-scope="scope">
<template v-if="scope.row.distributionMark === 'F'">
<el-button type="text" size="mini" @click="fillSellingPriceFromConfig(scope.row)">填价</el-button>
<el-button type="text" size="mini" @click="recalcOrderProfitOnly(scope.row)">重算</el-button>
</template>
<template v-else-if="scope.row.distributionMark === 'H-TF'">
<el-button type="text" size="mini" @click="recalcOrderProfitOnly(scope.row)">重算</el-button>
</template>
<span v-else style="color: #c0c4cc;"></span>
</template>
</el-table-column>
<el-table-column label="后返备注" prop="rebateRemarkJson" min-width="200" show-overflow-tooltip>
<template slot-scope="scope">
<template v-if="!scope.row.rebateRemarkJson">
@@ -980,7 +1063,7 @@
</template>
<script>
import { listJDOrders, updateJDOrder, delJDOrder, fetchLogisticsManually, batchMarkRebateReceived, generateExcelText, importGroupRebateExcelBatch, listGroupRebateExcelUploads, deleteGroupRebateUpload } from '@/api/system/jdorder'
import { listJDOrders, updateJDOrder, delJDOrder, fetchLogisticsManually, batchMarkRebateReceived, generateExcelText, importGroupRebateExcelBatch, listGroupRebateExcelUploads, deleteGroupRebateUpload, recalcProfitBatch } from '@/api/system/jdorder'
import { fillLogisticsByOrderNo, getTokenStatus, getTencentDocAuthUrl, testUserInfo, getAutoWriteConfig, reverseSyncThirdPartyOrderNo } from '@/api/jarvis/tendoc'
import { mapGetters } from 'vuex'
import ListLayout from '@/components/ListLayout'
@@ -1091,6 +1174,26 @@ export default {
}
return false
},
profitSummaryRows() {
return this.selectedRows && this.selectedRows.length > 0 ? this.selectedRows : this.list
},
profitSummaryLabel() {
return this.selectedRows && this.selectedRows.length > 0
? `已选 ${this.selectedRows.length}`
: `本页 ${this.list.length}`
},
profitSummaryRebateTotal() {
return this.profitSummaryRows.reduce((s, r) => s + (Number(r.rebateAmount) || 0), 0)
},
profitSummaryProfitTotal() {
let has = false
const t = this.profitSummaryRows.reduce((s, r) => {
if (r.profit == null || r.profit === '') return s
has = true
return s + (Number(r.profit) || 0)
}, 0)
return has ? t : ''
},
actionButtons() {
// 移动端只保留推送监控按钮
if (this.isMobile) {
@@ -1104,9 +1207,9 @@ export default {
{ key: 'config', label: '腾峰文档配置', type: 'warning', icon: 'el-icon-setting', handler: () => { this.showAutoWriteConfig = true } },
{ key: 'monitor', label: '推送监控', type: 'success', icon: 'el-icon-monitor', handler: () => { this.showPushMonitor = true } },
{ key: 'touser', label: '接收人配置', type: 'warning', icon: 'el-icon-user', handler: () => { this.showTouserConfig = true } },
{ key: 'sync', label: '一键发货到腾峰', type: 'warning', icon: 'el-icon-refresh-right', handler: () => this.handleBatchSyncLogistics(), loading: this.batchSyncLoading },
{ key: 'mark', label: '批量标记后返到账', type: 'warning', icon: 'el-icon-check', handler: () => this.handleBatchMarkRebateReceived(), loading: this.batchMarkLoading },
{ key: 'reverse', label: '反向同步第三方单号', type: 'warning', icon: 'el-icon-sort', handler: () => this.handleReverseSyncThirdPartyOrderNo(), loading: this.reverseSyncLoading }
// { key: 'sync', label: '一键发货到腾峰', type: 'warning', icon: 'el-icon-refresh-right', handler: () => this.handleBatchSyncLogistics(), loading: this.batchSyncLoading },
// { key: 'mark', label: '批量标记后返到账', type: 'warning', icon: 'el-icon-check', handler: () => this.handleBatchMarkRebateReceived(), loading: this.batchMarkLoading },
// { key: 'reverse', label: '反向同步第三方单号', type: 'warning', icon: 'el-icon-sort', handler: () => this.handleReverseSyncThirdPartyOrderNo(), loading: this.reverseSyncLoading }
]
}
},
@@ -1195,20 +1298,25 @@ export default {
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
},
/** 列表行默认值(退款开关、利润手填标记等) */
normalizeOrderListItem(item) {
return {
...item,
isRefunded: item.isRefunded != null ? item.isRefunded : 0,
isRefundReceived: item.isRefundReceived != null ? item.isRefundReceived : 0,
isRebateReceived: item.isRebateReceived != null ? item.isRebateReceived : 0,
isPriceProtected: item.isPriceProtected != null ? item.isPriceProtected : 0,
isInvoiceOpened: item.isInvoiceOpened != null ? item.isInvoiceOpened : 0,
isReviewPosted: item.isReviewPosted != null ? item.isReviewPosted : 0,
sellingPriceManual: item.sellingPriceManual != null ? item.sellingPriceManual : 0,
profitManual: item.profitManual != null ? item.profitManual : 0
}
},
getList() {
this.loading = true
listJDOrders(this.queryParams).then(res => {
const list = (res.rows || res.data || [])
// 为退款相关字段设置默认值
this.list = list.map(item => ({
...item,
isRefunded: item.isRefunded != null ? item.isRefunded : 0,
isRefundReceived: item.isRefundReceived != null ? item.isRefundReceived : 0,
isRebateReceived: item.isRebateReceived != null ? item.isRebateReceived : 0,
isPriceProtected: item.isPriceProtected != null ? item.isPriceProtected : 0,
isInvoiceOpened: item.isInvoiceOpened != null ? item.isInvoiceOpened : 0,
isReviewPosted: item.isReviewPosted != null ? item.isReviewPosted : 0
}))
this.list = list.map(item => this.normalizeOrderListItem(item))
this.total = res.total || 0
this.loading = false
}).catch(() => { this.loading = false })
@@ -1220,16 +1328,7 @@ export default {
try {
const res = await listJDOrders(this.queryParams)
let list = (res.rows || res.data || [])
// 为退款相关字段设置默认值
list = list.map(item => ({
...item,
isRefunded: item.isRefunded != null ? item.isRefunded : 0,
isRefundReceived: item.isRefundReceived != null ? item.isRefundReceived : 0,
isRebateReceived: item.isRebateReceived != null ? item.isRebateReceived : 0,
isPriceProtected: item.isPriceProtected != null ? item.isPriceProtected : 0,
isInvoiceOpened: item.isInvoiceOpened != null ? item.isInvoiceOpened : 0,
isReviewPosted: item.isReviewPosted != null ? item.isReviewPosted : 0
}))
list = list.map(item => this.normalizeOrderListItem(item))
this.list = list
this.total = res.total || 0
@@ -1251,15 +1350,7 @@ export default {
const yesterdayRes = await listJDOrders(this.queryParams)
let yesterdayList = (yesterdayRes.rows || yesterdayRes.data || [])
// 为退款相关字段设置默认值
yesterdayList = yesterdayList.map(item => ({
...item,
isRefunded: item.isRefunded != null ? item.isRefunded : 0,
isRefundReceived: item.isRefundReceived != null ? item.isRefundReceived : 0,
isRebateReceived: item.isRebateReceived != null ? item.isRebateReceived : 0,
isPriceProtected: item.isPriceProtected != null ? item.isPriceProtected : 0,
isInvoiceOpened: item.isInvoiceOpened != null ? item.isInvoiceOpened : 0,
isReviewPosted: item.isReviewPosted != null ? item.isReviewPosted : 0
}))
yesterdayList = yesterdayList.map(item => this.normalizeOrderListItem(item))
this.list = yesterdayList
this.total = yesterdayRes.total || 0
@@ -1490,6 +1581,54 @@ export default {
if (Number.isNaN(num)) return n
return num.toFixed(2)
},
persistOrderRow(row, successMsg) {
return updateJDOrder(row)
.then(() => {
if (successMsg) this.$message.success(successMsg)
})
.catch(() => {
this.$message.error('保存失败')
this.getList()
})
},
onOrderSellingPriceTypeChange(row) {
row.sellingPriceManual = 0
this.persistOrderRow(row)
},
onOrderSellingPriceChange(row) {
row.sellingPriceManual = 1
this.persistOrderRow(row)
},
onOrderProfitChange(row) {
row.profitManual = 1
this.persistOrderRow(row)
},
fillSellingPriceFromConfig(row) {
row.sellingPriceManual = 0
this.persistOrderRow(row, '已按型号配置回填售价并重算利润')
},
recalcOrderProfitOnly(row) {
row.profitManual = 0
this.persistOrderRow(row, '已重算利润')
},
handleBatchRecalcProfit() {
if (!this.selectedRows || this.selectedRows.length === 0) {
this.$message.warning('请先勾选订单')
return
}
this.loading = true
recalcProfitBatch(this.selectedRows.map(r => r.id))
.then(() => {
this.$message.success('批量重算完成')
this.getList()
})
.catch((e) => {
this.$message.error((e && e.message) || '批量重算失败')
})
.finally(() => {
this.loading = false
})
},
/** 导出按钮操作 */
handleExport() {
this.prepareQueryParams()
@@ -2194,15 +2333,9 @@ export default {
}
}
// 售价固定为0
const sellingPriceStr = '0'
// 成本(售价 - 利润售价是0
const rebate = row.rebateAmount || 0
const costStr = '' // 售价是0成本也设为空
// 利润(后返金额)
const profitStr = ''
const sellingPriceStr = row.sellingPrice != null && row.sellingPrice !== '' ? Number(row.sellingPrice).toFixed(2) : '0'
const costStr = ''
const profitStr = row.profit != null && row.profit !== '' ? Number(row.profit).toFixed(2) : ''
// 京东单号
const orderId = row.orderId || ''
@@ -2471,14 +2604,9 @@ export default {
}
}
// 售价固定为0
const sellingPriceStr = '0'
// 成本(售价 - 利润售价是0
const costStr = '' // 售价是0成本也设为空
// 利润(后返金额)
const profitStr = ''
const sellingPriceStr = row.sellingPrice != null && row.sellingPrice !== '' ? Number(row.sellingPrice).toFixed(2) : '0'
const costStr = ''
const profitStr = row.profit != null && row.profit !== '' ? Number(row.profit).toFixed(2) : ''
// 京东单号
const orderId = row.orderId || ''
@@ -3074,6 +3202,25 @@ export default {
margin-top: 12px;
}
}
.profit-summary-bar {
margin: 10px 0;
padding: 8px 12px;
background: #f4f6f9;
border-radius: 4px;
font-size: 13px;
color: #606266;
}
.profit-summary-item {
margin-right: 20px;
}
.mobile-profit-summary {
font-size: 12px;
line-height: 1.5;
}
.mobile-profit-summary div + div {
margin-top: 4px;
}
</style>