修改测算逻辑

This commit is contained in:
wuweihua
2026-09-18 17:09:43 +08:00
parent e08a36de91
commit bb240746a6
22 changed files with 435 additions and 44 deletions
+9
View File
@@ -29,6 +29,15 @@ export function listProductCountries(productCode, effectiveDate, productId) {
})
}
/** 修改产品国家明细行(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期) */
export function updateProductCountry(data) {
return request({
url: '/price/product/country',
method: 'put',
data: data
})
}
/** 新增产品 */
export function addProduct(data) {
return request({
+9
View File
@@ -165,6 +165,15 @@ export function listSetDetail(query) {
})
}
/** 版本明细搜索下拉选项:按版本去重后的 产品/国家/分区 */
export function listSetDetailOptions(psvId) {
return request({
url: '/price/set/detail/options',
method: 'get',
params: { psvId }
})
}
export function addSetDetail(data) {
return request({
url: '/price/set/detail',
@@ -11,6 +11,7 @@
max-height="calc(100vh - 120px)"
empty-text="暂无国家明细"
:span-method="spanMethod"
row-key="detailId"
>
<template #col-countryName="{ row }">
<span class="pre-wrap">{{ row.countryName }} ({{ row.countryCode }})</span>
@@ -21,15 +22,78 @@
<template #col-lastMile="{ row }">
<span class="pre-wrap">{{ row.lastMile }}</span>
</template>
<template #col-action="{ row }">
<el-button link type="primary" icon="Edit" @click="openEdit(row)" v-hasPermi="['price:product:edit']">编辑</el-button>
</template>
</simple-table>
<!-- 编辑国家明细弹窗 -->
<el-dialog v-model="editVisible" :title="editTitle" width="620px" append-to-body :close-on-click-modal="false">
<div class="edit-context">国家 / 分区:<b>{{ editRow.countryName }} ({{ editRow.countryCode }})</b> · {{ editRow.zoneName || '无分区' }}</div>
<el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="110px">
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="重量下限(KG)" prop="weightMin">
<el-input-number v-model="editForm.weightMin" :min="0" :precision="3" :step="0.1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="重量上限(KG)" prop="weightMax">
<el-input-number v-model="editForm.weightMax" :min="0" :precision="3" :step="0.1" controls-position="right" placeholder="留空为不封顶" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="运费(RMB/KG)" prop="freightPrice">
<el-input-number v-model="editForm.freightPrice" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="挂号费(RMB/票)" prop="registrationFee">
<el-input-number v-model="editForm.registrationFee" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="尾程" prop="lastMile">
<el-input v-model="editForm.lastMile" placeholder="如 DHL Paket" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="时效" prop="leadTime">
<el-input v-model="editForm.leadTime" placeholder="如 7-9工作日" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="生效日期" prop="effectiveDate">
<el-date-picker v-model="editForm.effectiveDate" type="date" value-format="YYYY-MM-DD" placeholder="选择日期" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="失效日期" prop="expireDate">
<el-date-picker v-model="editForm.expireDate" type="date" value-format="YYYY-MM-DD" placeholder="留空为长期" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="尺寸限制" prop="sizeLimit">
<el-input v-model="editForm.sizeLimit" type="textarea" :rows="2" placeholder="如 最大尺寸 60x50x40cm" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="editVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="saveEdit">保存</el-button>
</template>
</el-dialog>
</el-drawer>
</template>
<script setup>
import SimpleTable from '@/components/SimpleTable'
import { listProductCountries } from '@/api/price/product'
import { listProductCountries, updateProductCountry } from '@/api/price/product'
import { formatWeightLabel } from '@/views/price/set/utils/priceSet'
const { proxy } = getCurrentInstance()
const columns = [
{ prop: 'countryName', label: '国家', width: 120, showOverflowTooltip: true },
// { prop: 'countryCode', label: 'ISO2', width: 60, align: 'center' },
@@ -50,7 +114,8 @@ const columns = [
{ prop: 'leadTime', label: '时效', width: 100, showOverflowTooltip: true },
{ prop: 'effectiveDate', label: '生效日期', width: 110, align: 'center' },
{ prop: 'expireDate', label: '失效日期', width: 110, align: 'center', formatter: (row) => row.expireDate || '长期' },
{ prop: 'status', label: '状态', width: 90, align: 'center', dict: 'bms_product_effect_status' }
{ prop: 'status', label: '状态', width: 90, align: 'center', dict: 'bms_product_effect_status' },
{ prop: 'action', label: '操作', width: 70, fixed: 'right', slot: 'action' }
]
const visible = ref(false)
@@ -59,6 +124,8 @@ const loading = ref(false)
const list = ref([])
const keyword = ref('')
const status = ref('')
// 当前产品行(用于编辑保存后重新加载明细)
const currentProduct = ref(null)
const filteredList = computed(() => {
const kw = keyword.value.trim().toLowerCase()
@@ -78,13 +145,10 @@ function formatEffDate(v) {
return String(v).slice(0, 10)
}
function open(row) {
title.value = row.productCode + ' · ' + row.productName + ' · 国家服务明细'
visible.value = true
function load() {
if (!currentProduct.value) return
const row = currentProduct.value
loading.value = true
list.value = []
keyword.value = ''
status.value = ''
listProductCountries(row.productCode, formatEffDate(row.effectiveDate), row.productId).then(res => {
list.value = (res.data || []).slice().sort((a, b) => {
const c = String(a.countryCode || '').localeCompare(String(b.countryCode || ''))
@@ -96,6 +160,84 @@ function open(row) {
})
}
function open(row) {
title.value = row.productCode + ' · ' + row.productName + ' · 国家服务明细'
currentProduct.value = row
visible.value = true
list.value = []
keyword.value = ''
status.value = ''
load()
}
/* ---------- 编辑明细 ---------- */
const editVisible = ref(false)
const editTitle = ref('编辑国家明细')
const editFormRef = ref()
const saving = ref(false)
const editRow = ref({})
const editForm = reactive({
detailId: null,
weightMin: null,
weightMax: null,
freightPrice: null,
registrationFee: null,
sizeLimit: '',
lastMile: '',
leadTime: '',
effectiveDate: '',
expireDate: ''
})
const editRules = {
weightMin: [{ required: true, message: '重量下限不能为空', trigger: 'blur' }],
weightMax: [{
validator: (rule, value, callback) => {
if (value != null && editForm.weightMin != null && Number(value) <= Number(editForm.weightMin)) {
callback(new Error('上限需大于下限'))
} else {
callback()
}
},
trigger: 'blur'
}],
freightPrice: [{ required: true, message: '运费不能为空', trigger: 'blur' }],
effectiveDate: [{ required: true, message: '生效日期不能为空', trigger: 'change' }]
}
function openEdit(row) {
editRow.value = row
editTitle.value = `编辑明细 · ${row.countryName}${row.zoneName ? ' · ' + row.zoneName : ''} · ${formatWeightLabel(row.weightMin, row.weightMax) || ''}`
Object.assign(editForm, {
detailId: row.detailId,
weightMin: row.weightMin ?? null,
weightMax: row.weightMax ?? null,
freightPrice: row.freightPrice ?? null,
registrationFee: row.registrationFee ?? null,
sizeLimit: row.sizeLimit || '',
lastMile: row.lastMile || '',
leadTime: row.leadTime || '',
effectiveDate: row.effectiveDate ? String(row.effectiveDate).slice(0, 10) : '',
expireDate: row.expireDate ? String(row.expireDate).slice(0, 10) : ''
})
editVisible.value = true
nextTick(() => editFormRef.value?.clearValidate())
}
function saveEdit() {
editFormRef.value.validate(valid => {
if (!valid) return
saving.value = true
updateProductCountry({ ...editForm, expireDate: editForm.expireDate || null }).then(() => {
proxy.$modal.msgSuccess('修改成功')
editVisible.value = false
load()
}).finally(() => {
saving.value = false
})
})
}
function rowGroupKey(row) {
return String(row?.countryCode || '') + '|' + String(row?.zoneCode || '') + '|' + String(row?.countryName || '')
}
@@ -119,4 +261,12 @@ defineExpose({ open })
<style scoped>
.mb8 { margin-bottom: 8px; }
.pre-wrap { white-space: pre-wrap; font-size: 12px; line-height: 1.4; }
.edit-context {
margin-bottom: 14px;
padding: 8px 12px;
background: var(--el-fill-color-light);
border-radius: 4px;
font-size: 13px;
color: var(--el-text-color-regular);
}
</style>
@@ -36,9 +36,9 @@
</template>
<template #col-action="{ row }">
<el-button link type="primary" icon="View" @click="countryRef.open(row)" v-hasPermi="['price:product:query']">产品明细</el-button>
<el-button link type="primary" icon="Money" @click="surchargeRef.open(row)" v-hasPermi="['price:surcharge:query']">附加费</el-button>
<el-button :disabled="isProductActive(row)" :title="isProductActive(row) ? '已生效不可编辑' : ''" link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['price:product:edit']">编辑</el-button>
<el-button link type="primary" title="产品明细" icon="View" @click="countryRef.open(row)" v-hasPermi="['price:product:query']"></el-button>
<el-button link type="primary" title="附加费" icon="Money" @click="surchargeRef.open(row)" v-hasPermi="['price:surcharge:query']"></el-button>
<el-button title="编辑" link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['price:product:edit']"></el-button>
</template>
</basic-table>
@@ -114,10 +114,6 @@ function handleAdd() {
}
function handleUpdate(row) {
if (isProductActive(row)) {
proxy.$modal.msgError('已生效的产品不可编辑')
return
}
formRef.value.open(row)
}
@@ -9,8 +9,6 @@
</div>
</template>
<el-alert type="info" :closable="false" show-icon class="mb12" :title="metaText" />
<div class="filters">
<el-input v-model="query.keyword" clearable :placeholder="phKeyword" style="width: 180px" />
<el-select v-model="query.taxMode" clearable :placeholder="phTax" style="width: 140px">
@@ -148,11 +146,6 @@ const discCode = ref('')
const typeHint = computed(() => '· 当前:' + setTypeLabel(props.setType))
const metaText = computed(() => {
const ver = props.versionNo || 'V?'
return ver + ' · 数据已落库,编辑价格/附加费会立即写入数据库。'
})
const productGroups = computed(() => {
const map = new Map()
for (const d of props.details) {
@@ -8,29 +8,47 @@
>
<div class="drawer-body" v-loading="loading">
<div class="toolbar">
<el-input
<el-select
v-model="query.productKeyword"
filterable
clearable
:placeholder="phProduct"
style="width: 160px"
@keyup.enter="search"
/>
<el-input
:loading="optionsLoading"
style="width: 200px"
>
<el-option v-for="p in optionProducts" :key="p.code" :label="p.label" :value="p.code">
<span class="opt-name">{{ p.name }}</span>
<span class="opt-code">{{ p.code }}</span>
</el-option>
</el-select>
<el-select
v-if="isFixed"
v-model="query.countryKeyword"
filterable
clearable
:placeholder="phCountry"
style="width: 140px"
@keyup.enter="search"
/>
<el-input
:loading="optionsLoading"
style="width: 160px"
>
<el-option v-for="c in optionCountries" :key="c.key" :label="c.label" :value="c.value">
<span class="opt-name">{{ c.name }}</span>
<span class="opt-code">{{ c.code }}</span>
</el-option>
</el-select>
<el-select
v-if="isFixed"
v-model="query.zoneKeyword"
filterable
clearable
:placeholder="phZone"
style="width: 140px"
@keyup.enter="search"
/>
:loading="optionsLoading"
style="width: 160px"
>
<el-option v-for="z in optionZones" :key="z.key" :label="z.label" :value="z.value">
<span class="opt-name">{{ z.name }}</span>
<span class="opt-code">{{ z.code }}</span>
</el-option>
</el-select>
<el-button type="primary" @click="search">查询</el-button>
<el-button @click="reset">重置</el-button>
</div>
@@ -110,7 +128,7 @@
<script setup name="SetVersionDetailDrawer">
import SurchargeTags from '../SurchargeTags.vue'
import { isFixedType } from '../../utils/priceSet'
import { listSetDetail } from '@/api/price/set'
import { listSetDetail, listSetDetailOptions } from '@/api/price/set'
const props = defineProps({
modelValue: { type: Boolean, default: false },
@@ -132,7 +150,48 @@ const query = reactive({
zoneKeyword: ''
})
const phProduct = '产品代码/名称'
// 下拉选项:当前版本内去重后的 产品/国家/分区
const optionsLoading = ref(false)
const optionProducts = ref([])
const optionCountries = ref([])
const optionZones = ref([])
function loadOptions() {
const psvId = props.version && props.version.psvId
if (!psvId) {
optionProducts.value = []
optionCountries.value = []
optionZones.value = []
return
}
optionsLoading.value = true
listSetDetailOptions(psvId).then(res => {
const data = res.data || {}
optionProducts.value = (data.products || []).map(p => ({
code: p.code,
name: p.name || p.code,
label: (p.name && p.name !== p.code) ? `${p.code} ${p.name}` : p.code
}))
optionCountries.value = (data.countries || []).map(c => ({
key: c.code || c.name,
code: c.code || '-',
name: c.name || c.code,
value: c.code || c.name, // 有代码按代码搜,否则按名称搜(后端 code/name 双 LIKE)
label: (c.name && c.code) ? `${c.name} ${c.code}` : (c.name || c.code)
}))
optionZones.value = (data.zones || []).map(z => ({
key: z.code || z.name,
code: z.code || '-',
name: z.name || z.code,
value: z.code || z.name,
label: (z.name && z.code) ? `${z.name} ${z.code}` : (z.name || z.code)
}))
}).finally(() => {
optionsLoading.value = false
})
}
const phProduct = '产品'
const phCountry = '国家'
const phZone = '分区'
const colProduct = '产品'
@@ -205,6 +264,7 @@ watch(
query.productKeyword = ''
query.countryKeyword = ''
query.zoneKeyword = ''
loadOptions()
load()
}
}
@@ -242,6 +302,13 @@ watch(
padding: 0 6px;
}
.p-name { color: #303133; }
.opt-name { margin-right: 8px; }
.opt-code {
float: right;
color: var(--el-text-color-secondary);
font-size: 12px;
font-family: Consolas, Monaco, monospace;
}
.hl { color: #1890ff; font-weight: 600; }
.reg { color: #d48806; font-weight: 600; }
.pct { color: #1890ff; font-weight: 600; }
@@ -28,7 +28,7 @@
<el-table-column :label="colRows" width="100" align="center">
<template #default="{ row }">{{ detailCount(row) }}</template>
</el-table-column>
<el-table-column :label="colAction" width="320" align="center" fixed="right">
<el-table-column :label="colAction" width="360" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="openDetails(row)">查看详情</el-button>
<el-button
@@ -38,6 +38,12 @@
@click="emit('edit', row)"
v-hasPermi="['price:set:edit']"
>继续配置</el-button>
<el-button
link
type="warning"
@click="emit('edit', row)"
v-hasPermi="['price:set:forceEdit']"
>编辑</el-button>
<el-button
v-if="isDraftVersion(row)"
link
@@ -99,8 +105,8 @@ function tabLabel(key) {
all: '全部',
'0': '草稿',
'3': '待生效',
'1': '生效',
'2': '失效'
'1': '已生效',
'2': '已失效'
}
return labels[key] + ' (' + (counts.value[key] || 0) + ')'
}