Files
BMS/bms-front/src/views/price/basic/pay/components/PayFeeRuleDialog.vue
T
2026-11-05 16:05:01 +08:00

292 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<el-dialog
v-model="visible"
title="附加费配置"
width="1280px"
top="6vh"
append-to-body
destroy-on-close
@closed="reset"
>
<div v-loading="loading">
<p class="fee-tip">还没配置的产品共用一页,保存后按产品各写一份。已经配置过的产品单独一页,互不影响。</p>
<el-tabs v-if="tabs.length > 1" v-model="active">
<el-tab-pane v-for="tab in tabs" :key="tab.key" :label="tab.label" :name="tab.key" />
</el-tabs>
<div v-if="current" class="fee-bar">
<span>{{ currentHint }}</span>
<el-button type="primary" plain icon="Plus" @click="addRow">添加一行</el-button>
</div>
<el-table v-if="current" :data="current.rules" border size="small" max-height="460" empty-text="还没有附加费">
<el-table-column label="附加费名称" min-width="180">
<template #default="{ row }">
<el-select v-model="row.feeName" filterable clearable placeholder="选择附加费">
<el-option v-for="item in feeOptions(row)" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
</el-table-column>
<el-table-column label="启用" width="72" align="center">
<template #default="{ row }">
<el-switch v-model="row.enabled" active-value="1" inactive-value="0" />
</template>
</el-table-column>
<el-table-column label="触发类型" width="120">
<template #default="{ row }">
<el-select v-model="row.triggerType">
<el-option v-for="item in triggerOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</template>
</el-table-column>
<el-table-column label="最长边cm" width="108">
<template #default="{ row }"><el-input-number v-model="row.sideMaxCm" :min="0" :controls="false" :value-on-clear="null" /></template>
</el-table-column>
<el-table-column label="长+宽+高cm" width="116">
<template #default="{ row }"><el-input-number v-model="row.sumMaxCm" :min="0" :controls="false" :value-on-clear="null" /></template>
</el-table-column>
<el-table-column label="体积cm³" width="116">
<template #default="{ row }"><el-input-number v-model="row.volumeMaxCm3" :min="0" :controls="false" :value-on-clear="null" /></template>
</el-table-column>
<el-table-column label="最小长" width="96">
<template #default="{ row }"><el-input-number v-model="row.minLengthCm" :min="0" :controls="false" :value-on-clear="null" /></template>
</el-table-column>
<el-table-column label="最小宽" width="96">
<template #default="{ row }"><el-input-number v-model="row.minWidthCm" :min="0" :controls="false" :value-on-clear="null" /></template>
</el-table-column>
<el-table-column label="最小高" width="96">
<template #default="{ row }"><el-input-number v-model="row.minHeightCm" :min="0" :controls="false" :value-on-clear="null" /></template>
</el-table-column>
<el-table-column label="重量上限kg" width="112">
<template #default="{ row }"><el-input-number v-model="row.weightMaxKg" :min="0" :precision="3" :controls="false" :value-on-clear="null" /></template>
</el-table-column>
<el-table-column label="命中方式" width="128">
<template #default="{ row }">
<el-select v-model="row.joinMode">
<el-option label="任一超出" value="ANY" />
<el-option label="全部超出" value="ALL" />
</el-select>
</template>
</el-table-column>
<el-table-column label="说明" min-width="150">
<template #default="{ row }">
<el-input v-model="row.remark" maxlength="500" placeholder="不参与判断" />
</template>
</el-table-column>
<el-table-column label="操作" width="70" align="center" fixed="right">
<template #default="{ $index }">
<el-button link type="danger" @click="removeRow($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { listFeeRule, saveFeeRule } from '@/api/price/pay'
import { useDict } from '@/utils/dict'
const { proxy } = getCurrentInstance()
const { bms_surcharge_type } = useDict('bms_surcharge_type')
const visible = ref(false)
const loading = ref(false)
const saving = ref(false)
const tabs = ref([])
const active = ref('')
const triggerOptions = [
{ label: '尺寸', value: 'SIZE' },
{ label: '体积', value: 'VOLUME' },
{ label: '重量', value: 'WEIGHT' },
{ label: '邮编', value: 'POSTCODE' },
{ label: '每票', value: 'ALWAYS' },
{ label: '退件', value: 'RETURN' }
]
const current = computed(() => tabs.value.find(tab => tab.key === active.value) || null)
const currentHint = computed(() => {
if (!current.value) return ''
if (current.value.productIds.length > 1) return `同时写入:${current.value.productName}`
return tabs.value.length === 1 ? current.value.productName : ''
})
function blankRule() {
return {
feeName: '',
enabled: '0',
triggerType: 'ALWAYS',
sideMaxCm: null,
sumMaxCm: null,
volumeMaxCm3: null,
minLengthCm: null,
minWidthCm: null,
minHeightCm: null,
weightMaxKg: null,
joinMode: 'ANY',
remark: ''
}
}
function copyRule(row) {
const next = blankRule()
next.feeName = row.feeName || ''
next.enabled = row.enabled === '1' ? '1' : '0'
next.triggerType = row.triggerType || 'ALWAYS'
next.sideMaxCm = toNumber(row.sideMaxCm)
next.sumMaxCm = toNumber(row.sumMaxCm)
next.volumeMaxCm3 = toNumber(row.volumeMaxCm3)
next.minLengthCm = toNumber(row.minLengthCm)
next.minWidthCm = toNumber(row.minWidthCm)
next.minHeightCm = toNumber(row.minHeightCm)
next.weightMaxKg = toNumber(row.weightMaxKg)
next.joinMode = row.joinMode || 'ANY'
next.remark = row.remark || ''
return next
}
function toNumber(value) {
if (value === null || value === undefined || value === '') return null
const number = Number(value)
return Number.isFinite(number) ? number : null
}
function feeOptions(row) {
const list = bms_surcharge_type.value || []
if (row.feeName && !list.some(item => item.value === row.feeName)) {
return [{ label: row.feeName, value: row.feeName }, ...list]
}
return list
}
function addRow() {
current.value?.rules.push(blankRule())
}
function removeRow(index) {
if (!current.value) return
current.value.rules.splice(index, 1)
if (!current.value.rules.length) current.value.rules.push(blankRule())
}
function rowTouched(row) {
return !!(row.feeName || row.remark || row.enabled === '1'
|| (row.triggerType && row.triggerType !== 'ALWAYS')
|| (row.joinMode && row.joinMode !== 'ANY')
|| [row.sideMaxCm, row.sumMaxCm, row.volumeMaxCm3, row.minLengthCm, row.minWidthCm, row.minHeightCm, row.weightMaxKg]
.some(value => value !== null && value !== undefined && value !== ''))
}
function keptRules(tab) {
const kept = []
const names = new Set()
for (const row of tab.rules) {
if (!rowTouched(row)) continue
const feeName = (row.feeName || '').trim()
if (!feeName) {
proxy.$modal.msgError(`${tab.productName || '产品'}有未选择名称的附加费`)
return null
}
if (names.has(feeName)) {
proxy.$modal.msgError(`${tab.productName || '产品'}的附加费「${feeName}」重复了`)
return null
}
names.add(feeName)
kept.push({ ...row, feeName, remark: (row.remark || '').trim() })
}
return kept
}
function save() {
const products = []
for (const tab of tabs.value) {
const rules = keptRules(tab)
if (!rules) return
for (const productId of tab.productIds) {
products.push({ productId, rules: rules.map(row => ({ ...row })) })
}
}
if (!products.length) return
saving.value = true
saveFeeRule(products).then(() => {
proxy.$modal.msgSuccess('附加费配置已保存')
visible.value = false
}).finally(() => {
saving.value = false
})
}
function reset() {
tabs.value = []
active.value = ''
loading.value = false
saving.value = false
}
function open(rows) {
const picked = (rows || []).filter(row => row && row.productId != null)
if (!picked.length) return
reset()
visible.value = true
loading.value = true
listFeeRule(picked.map(row => row.productId)).then(res => {
const groups = res.data || []
const unbound = groups.filter(group => !group.rules?.length)
const bound = groups.filter(group => group.rules?.length)
const next = []
if (unbound.length) {
const names = unbound.map(group => group.productName || `产品${group.productId}`)
next.push({
key: 'unbound',
label: '未配置',
productIds: unbound.map(group => group.productId),
productName: names.join('、'),
rules: [blankRule()]
})
}
const nameCount = bound.reduce((map, group) => {
const name = group.productName || ''
map.set(name, (map.get(name) || 0) + 1)
return map
}, new Map())
bound.forEach(group => {
const name = group.productName || `产品${group.productId}`
next.push({
key: `bound-${group.productId}`,
label: nameCount.get(group.productName || '') > 1 ? `${name}(${group.productId})` : name,
productIds: [group.productId],
productName: name,
rules: group.rules.map(copyRule)
})
})
tabs.value = next
active.value = next.length ? next[0].key : ''
}).catch(() => {
visible.value = false
}).finally(() => {
loading.value = false
})
}
defineExpose({ open })
</script>
<style scoped>
.fee-tip {
margin: 0 0 12px;
color: var(--el-text-color-secondary);
}
.fee-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
:deep(.el-input-number) {
width: 100%;
}
</style>