292 lines
10 KiB
Vue
292 lines
10 KiB
Vue
<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>
|