667 lines
25 KiB
Vue
667 lines
25 KiB
Vue
<template>
|
||
<div v-if="panes.length" class="pay-detail">
|
||
<section v-for="pane in panes" :key="pane.key" class="pay-product">
|
||
<div class="pay-product-title">{{ pane.item.productName || '未命名产品' }}</div>
|
||
<el-descriptions :column="3" border size="small" class="pay-summary">
|
||
<el-descriptions-item label="价卡归属">{{ pane.item.supplierName || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="生效日期">{{ pane.item.effectiveDate || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="国家/口岸">{{ pane.item.countryScope || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="费用类型">{{ pane.item.chargeType || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="每公斤加价">{{ pane.item.extraPerKg || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="服务类型">{{ pane.item.serviceType || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="分类">{{ pane.item.classification || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="币种">{{ pane.item.currency || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="报价工作表">{{ pane.item.sheetName || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="关联分区表">{{ pane.item.relatedSheet || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="识别结果">{{ statusText(pane.item.parseStatus) }}</el-descriptions-item>
|
||
<el-descriptions-item label="重量限制">{{ pane.item.weightLimit || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="更新内容" :span="2">{{ pane.item.updateContent || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="目录备注" :span="3">{{ pane.item.catalogRemark || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item v-if="pane.item.remark" label="备注" :span="3">{{ pane.item.remark }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
|
||
<el-tabs v-model="pane.tab">
|
||
<el-tab-pane :label="`重量段(${(pane.item.weightBands || []).length})`" name="band">
|
||
<el-input v-model="pane.bandKeyword" clearable placeholder="筛选国家、分区、重量段、费用项" class="pay-filter" @input="pane.bandPageNo = 1" />
|
||
<el-table :data="pageBands(pane)" border size="small" empty-text="没有重量段或起重配置">
|
||
<el-table-column prop="sectionName" label="区块" min-width="110" show-overflow-tooltip>
|
||
<template #default="{ row }"><cell-text v-model="row.sectionName" :edit="isRowEditing(row)" /></template>
|
||
</el-table-column>
|
||
<el-table-column prop="countryName" label="国家" min-width="120" show-overflow-tooltip>
|
||
<template #default="{ row }"><cell-text v-model="row.countryName" :edit="isRowEditing(row)" /></template>
|
||
</el-table-column>
|
||
<el-table-column prop="countryCode" label="代码" width="80" show-overflow-tooltip>
|
||
<template #default="{ row }"><cell-text v-model="row.countryCode" :edit="isRowEditing(row)" /></template>
|
||
</el-table-column>
|
||
<el-table-column prop="zoneName" label="分区" min-width="140" show-overflow-tooltip>
|
||
<template #default="{ row }"><cell-text v-model="row.zoneName" :edit="isRowEditing(row)" /></template>
|
||
</el-table-column>
|
||
<el-table-column prop="weightLabel" label="重量段" min-width="120" show-overflow-tooltip>
|
||
<template #default="{ row }"><cell-text v-model="row.weightLabel" :edit="isRowEditing(row)" /></template>
|
||
</el-table-column>
|
||
<el-table-column label="下限KG" width="90" align="right">
|
||
<template #default="{ row }">{{ money(row.weightMin) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="上限KG" width="90" align="right">
|
||
<template #default="{ row }">{{ money(row.weightMax) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="chargeName" label="费用项" min-width="120" show-overflow-tooltip>
|
||
<template #default="{ row }"><cell-text v-model="row.chargeName" :edit="isRowEditing(row)" /></template>
|
||
</el-table-column>
|
||
<el-table-column label="单价" width="110" align="right">
|
||
<template #default="{ row }">
|
||
<el-input v-if="isRowEditing(row)" v-model="row.price" size="small" />
|
||
<span v-else>{{ money(row.price) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="priceUnit" label="单位" width="110" show-overflow-tooltip>
|
||
<template #default="{ row }"><cell-text v-model="row.priceUnit" :edit="isRowEditing(row)" /></template>
|
||
</el-table-column>
|
||
<el-table-column v-if="editable" label="操作" width="120" align="center" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button v-if="!isRowEditing(row)" link type="primary" :disabled="rowBusy" @click="startEdit(row)">修改</el-button>
|
||
<el-button v-else link type="primary" :disabled="rowBusy" @click="saveBandRow(pane, row)">保存</el-button>
|
||
<el-button link type="danger" :disabled="rowBusy" @click="removeRow(pane.item.weightBands, row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-pagination
|
||
v-if="filteredBands(pane).length > pageSize"
|
||
class="pay-page"
|
||
layout="total, prev, pager, next"
|
||
:total="filteredBands(pane).length"
|
||
:page-size="pageSize"
|
||
v-model:current-page="pane.bandPageNo"
|
||
/>
|
||
</el-tab-pane>
|
||
|
||
<el-tab-pane :label="`附加费(${(pane.item.surcharges || []).length})`" name="fee">
|
||
<div class="pay-edit-bar">
|
||
<el-input v-model="pane.feeKeyword" clearable placeholder="筛选附加费用、地区、价格、货币或费用解释" class="pay-filter" @input="pane.feePageNo = 1" />
|
||
<el-button v-if="editable" size="small" type="primary" plain @click="addFee(pane)">添加附加费</el-button>
|
||
</div>
|
||
<el-table :data="pageFees(pane)" border size="small" empty-text="没有附加费">
|
||
<el-table-column prop="feeName" label="附加费用" min-width="180" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-if="isRowEditing(row)"
|
||
v-model="row.feeName"
|
||
filterable
|
||
clearable
|
||
size="small"
|
||
style="width: 100%"
|
||
:placeholder="'请选择附加费'"
|
||
@change="name => onFeeNameChange(row, name)"
|
||
>
|
||
<el-option
|
||
v-for="item in feeNameOptions(row)"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
<span v-else>{{ row.feeName || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="zoneName" label="地区" min-width="160" show-overflow-tooltip>
|
||
<template #default="{ row }"><cell-text v-model="row.zoneName" :edit="isRowEditing(row)" /></template>
|
||
</el-table-column>
|
||
<el-table-column label="价格" width="110" align="right">
|
||
<template #default="{ row }">
|
||
<el-input v-if="isRowEditing(row)" v-model="row.amount" size="small" />
|
||
<span v-else>{{ money(row.amount) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="币种" width="110" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-if="isRowEditing(row)"
|
||
:model-value="feeCurrencyOf(row)"
|
||
filterable
|
||
allow-create
|
||
default-first-option
|
||
clearable
|
||
size="small"
|
||
style="width: 100%"
|
||
:placeholder="'币种'"
|
||
@update:model-value="value => setFeeCurrency(row, value)"
|
||
>
|
||
<el-option
|
||
v-for="item in currencyOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
<span v-else>{{ feeCurrencyOf(row) || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="单位" width="110" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-if="isRowEditing(row)"
|
||
:model-value="feeUnitOf(row)"
|
||
filterable
|
||
allow-create
|
||
default-first-option
|
||
clearable
|
||
size="small"
|
||
style="width: 100%"
|
||
:placeholder="'单位'"
|
||
@update:model-value="value => setFeeUnit(row, value)"
|
||
>
|
||
<el-option
|
||
v-for="item in feeUnitOptions"
|
||
:key="item"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
<span v-else>{{ feeUnitOf(row) || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="收费标准" min-width="180" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
<cell-text v-if="isRowEditing(row) && (!row.chargeText || feeNote(row))" v-model="row.chargeText" :edit="true" />
|
||
<span v-else>{{ feeNote(row) || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="feeDesc" label="费用解释" min-width="240" show-overflow-tooltip>
|
||
<template #default="{ row }"><cell-text v-model="row.feeDesc" :edit="isRowEditing(row)" /></template>
|
||
</el-table-column>
|
||
<el-table-column prop="calcRule" label="计费条件" width="140">
|
||
<template #default="{ row }">
|
||
<el-select v-if="isRowEditing(row)" v-model="row.calcRule" size="small" placeholder="人工确认">
|
||
<el-option v-for="item in calcRules" :key="item.value" :label="item.label" :value="item.value" />
|
||
</el-select>
|
||
<span v-else>{{ calcRuleLabel(row.calcRule) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="calcParam" label="条件参数" min-width="220" show-overflow-tooltip>
|
||
<template #default="{ row }">
|
||
<span v-if="!isRowEditing(row)">{{ feeCondition(row) }}</span>
|
||
<template v-else>
|
||
<div v-if="conditionHint(row)" class="pay-cond-hint">{{ conditionHint(row) }}</div>
|
||
<cell-text
|
||
v-if="showCalcParamEditor(row)"
|
||
v-model="row.calcParam"
|
||
:edit="true"
|
||
/>
|
||
</template>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column v-if="editable" label="操作" width="120" align="center" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button v-if="!isRowEditing(row)" link type="primary" :disabled="rowBusy" @click="startEdit(row)">修改</el-button>
|
||
<el-button v-else link type="primary" :disabled="rowBusy" @click="saveFeeRow(pane, row)">保存</el-button>
|
||
<el-button link type="danger" :disabled="rowBusy" @click="removeRow(pane.item.surcharges, row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-pagination
|
||
v-if="filteredFees(pane).length > pageSize"
|
||
class="pay-page"
|
||
layout="total, prev, pager, next"
|
||
:total="filteredFees(pane).length"
|
||
:page-size="pageSize"
|
||
v-model:current-page="pane.feePageNo"
|
||
/>
|
||
</el-tab-pane>
|
||
|
||
<el-tab-pane v-if="postcodeLines(pane).length" :label="`邮编分区(${postcodeLines(pane).length})`" name="postcode">
|
||
<p class="pay-hint">{{ postcodeHint(pane) }}</p>
|
||
<el-input v-model="pane.postKeyword" clearable placeholder="筛选邮编、州、城市或分区" class="pay-filter" @input="pane.postPageNo = 1" />
|
||
<el-table :data="pagePostcodes(pane)" border size="small" empty-text="没有邮编">
|
||
<el-table-column v-for="(title, index) in postcodeTitles(pane)" :key="title" :label="title" min-width="110" show-overflow-tooltip>
|
||
<template #default="{ row }">{{ row.parts[index] || '-' }}</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-pagination
|
||
v-if="filteredPostcodes(pane).length > pageSize"
|
||
class="pay-page"
|
||
layout="total, prev, pager, next"
|
||
:total="filteredPostcodes(pane).length"
|
||
:page-size="pageSize"
|
||
v-model:current-page="pane.postPageNo"
|
||
/>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</section>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
// 重量段和附加费。传入 products 时一次展示全部产品。
|
||
import { h, onMounted, watch } from 'vue'
|
||
import { ElInput } from 'element-plus'
|
||
import { listSurchargeOptions } from '@/api/price/surcharge'
|
||
|
||
const props = defineProps({
|
||
product: {
|
||
type: Object,
|
||
default: null
|
||
},
|
||
products: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
editable: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
savingRowKey: {
|
||
type: [String, Number],
|
||
default: ''
|
||
}
|
||
})
|
||
|
||
const emit = defineEmits(['save-row'])
|
||
|
||
const editingKeys = ref(new Set())
|
||
|
||
watch(() => props.product, () => {
|
||
editingKeys.value = new Set()
|
||
})
|
||
|
||
function rowKeyOf(row) {
|
||
if (!row) return ''
|
||
if (row.surchargeId != null) return 'fee-' + row.surchargeId
|
||
if (row.bandId != null) return 'band-' + row.bandId
|
||
if (row.__rowKey) return row.__rowKey
|
||
row.__rowKey = 'tmp-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8)
|
||
return row.__rowKey
|
||
}
|
||
|
||
function isRowEditing(row) {
|
||
return editingKeys.value.has(rowKeyOf(row))
|
||
}
|
||
|
||
function startEdit(row) {
|
||
const next = new Set(editingKeys.value)
|
||
next.add(rowKeyOf(row))
|
||
editingKeys.value = next
|
||
}
|
||
|
||
function stopEdit(row) {
|
||
const next = new Set(editingKeys.value)
|
||
next.delete(rowKeyOf(row))
|
||
editingKeys.value = next
|
||
}
|
||
|
||
const localSavingKey = ref('')
|
||
const rowBusy = computed(() => !!(props.savingRowKey || localSavingKey.value))
|
||
|
||
function finishSave(row, ok) {
|
||
localSavingKey.value = ''
|
||
if (ok !== false) stopEdit(row)
|
||
}
|
||
|
||
function runSave(kind, pane, row) {
|
||
if (localSavingKey.value || props.savingRowKey) return
|
||
const rowKey = rowKeyOf(row)
|
||
localSavingKey.value = rowKey
|
||
let settled = false
|
||
const done = (ok) => {
|
||
if (settled) return
|
||
settled = true
|
||
finishSave(row, ok)
|
||
}
|
||
try {
|
||
emit('save-row', { kind, product: pane.item, row, rowKey, done })
|
||
} catch (e) {
|
||
done(false)
|
||
return
|
||
}
|
||
// Parent must call done(); hard stop UI lock if it does not.
|
||
window.setTimeout(() => {
|
||
if (!settled) done(false)
|
||
}, 8000)
|
||
}
|
||
|
||
function saveBandRow(pane, row) {
|
||
runSave('band', pane, row)
|
||
}
|
||
|
||
function saveFeeRow(pane, row) {
|
||
runSave('fee', pane, row)
|
||
}
|
||
|
||
const surchargeOptions = ref([])
|
||
const { bms_currency } = useDict('bms_currency')
|
||
const currencyOptions = computed(() => (bms_currency.value || []).map(item => ({
|
||
label: item.label || item.value,
|
||
value: item.value
|
||
})))
|
||
const feeUnitOptions = ['ITEM', 'KG', 'PCS', 'PARCEL', '%']
|
||
|
||
function parseFeeUnit(priceUnit) {
|
||
const text = String(priceUnit || '').trim()
|
||
if (!text) return ''
|
||
if (text === '%') return '%'
|
||
const slash = text.lastIndexOf('/')
|
||
if (slash >= 0) {
|
||
return text.slice(slash + 1).trim().toUpperCase()
|
||
}
|
||
if (/^[A-Za-z]{3}$/.test(text)) return ''
|
||
return text.toUpperCase()
|
||
}
|
||
|
||
function feeCurrencyOf(row) {
|
||
const currency = String(row?.currency || '').trim()
|
||
if (currency) return currency.toUpperCase()
|
||
const text = String(row?.priceUnit || '').trim()
|
||
const match = text.match(/^([A-Za-z]{3})(?:\/|$)/)
|
||
return match ? match[1].toUpperCase() : ''
|
||
}
|
||
|
||
function feeUnitOf(row) {
|
||
return parseFeeUnit(row?.priceUnit)
|
||
}
|
||
|
||
function syncFeePriceUnit(row, unitOverride) {
|
||
const currency = feeCurrencyOf(row)
|
||
const unit = unitOverride != null ? String(unitOverride || '').trim().toUpperCase() : feeUnitOf(row)
|
||
if (unit === '%') {
|
||
row.priceUnit = '%'
|
||
return
|
||
}
|
||
if (currency && unit) {
|
||
row.priceUnit = currency + '/' + unit
|
||
return
|
||
}
|
||
if (currency) {
|
||
row.priceUnit = currency
|
||
return
|
||
}
|
||
row.priceUnit = unit || ''
|
||
}
|
||
|
||
function setFeeCurrency(row, value) {
|
||
const currency = String(value || '').trim().toUpperCase()
|
||
row.currency = currency
|
||
syncFeePriceUnit(row)
|
||
}
|
||
|
||
function setFeeUnit(row, value) {
|
||
syncFeePriceUnit(row, value)
|
||
}
|
||
|
||
function loadSurchargeOptions() {
|
||
return listSurchargeOptions({ ssCode: 'on' }).then(res => {
|
||
surchargeOptions.value = res.data || []
|
||
}).catch(() => {
|
||
surchargeOptions.value = []
|
||
})
|
||
}
|
||
|
||
function feeNameOptions(row) {
|
||
const names = []
|
||
const seen = new Set()
|
||
for (const item of surchargeOptions.value || []) {
|
||
const name = String(item?.scName || '').trim()
|
||
if (!name || seen.has(name)) continue
|
||
seen.add(name)
|
||
names.push(name)
|
||
}
|
||
const current = String(row?.feeName || '').trim()
|
||
if (current && !seen.has(current)) {
|
||
names.unshift(current)
|
||
}
|
||
return names
|
||
}
|
||
|
||
function onFeeNameChange(row, name) {
|
||
const matched = (surchargeOptions.value || []).find(item => String(item?.scName || '').trim() === String(name || '').trim())
|
||
if (!matched) {
|
||
row.scId = undefined
|
||
return
|
||
}
|
||
row.scId = matched.scId
|
||
const currency = matched.currency || feeCurrencyOf(row)
|
||
if (currency) {
|
||
setFeeCurrency(row, currency)
|
||
}
|
||
if (!feeUnitOf(row)) {
|
||
setFeeUnit(row, 'ITEM')
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
loadSurchargeOptions()
|
||
})
|
||
|
||
const CellText = {
|
||
props: { modelValue: { default: '' }, edit: Boolean },
|
||
emits: ['update:modelValue'],
|
||
setup(props, { emit }) {
|
||
return () => props.edit
|
||
? h(ElInput, {
|
||
modelValue: props.modelValue ?? '',
|
||
size: 'small',
|
||
'onUpdate:modelValue': value => emit('update:modelValue', value)
|
||
})
|
||
: h('span', props.modelValue || '-')
|
||
}
|
||
}
|
||
|
||
const calcRules = [
|
||
{ value: 'ALWAYS', label: '每票都收' },
|
||
{ value: 'POSTCODE', label: '命中邮编' },
|
||
{ value: 'ZONE', label: '命中分区' },
|
||
{ value: 'EXTRA_KG', label: '超重续收' },
|
||
{ value: 'WEIGHT', label: '超重' },
|
||
{ value: 'SIZE', label: '超尺寸' },
|
||
{ value: 'RETURN', label: '退件' },
|
||
{ value: 'MANUAL', label: '人工确认' }
|
||
]
|
||
|
||
function calcRuleLabel(value) {
|
||
return calcRules.find(item => item.value === value)?.label || '人工确认'
|
||
}
|
||
|
||
function conditionHint(row) {
|
||
const parts = []
|
||
const hasMin = row.weightMin != null && row.weightMin !== ''
|
||
const hasMax = row.weightMax != null && row.weightMax !== ''
|
||
if (hasMin || hasMax) {
|
||
parts.push(`${hasMin ? row.weightMin : ''}-${hasMax ? row.weightMax : ''}kg`)
|
||
}
|
||
if (row.postcodeParam) {
|
||
parts.push(`邮编 ${row.postcodeParam}`)
|
||
}
|
||
return parts.join(';')
|
||
}
|
||
|
||
function hasCalcParam(row) {
|
||
const text = String(row?.calcParam ?? '').trim()
|
||
if (!text) return false
|
||
return text !== String(row?.postcodeParam ?? '').trim()
|
||
}
|
||
|
||
function showCalcParamEditor(row) {
|
||
return hasCalcParam(row) || !conditionHint(row)
|
||
}
|
||
|
||
function feeCondition(row) {
|
||
const parts = []
|
||
const hint = conditionHint(row)
|
||
if (hint) parts.push(hint)
|
||
if (hasCalcParam(row)) parts.push(String(row.calcParam).trim())
|
||
return parts.join(';') || '-'
|
||
}
|
||
|
||
const pageSize = 20
|
||
const panes = ref([])
|
||
|
||
const source = computed(() => (props.products?.length ? props.products : (props.product ? [props.product] : [])))
|
||
|
||
watch(source, (list) => {
|
||
panes.value = list.map((item, index) => ({
|
||
key: item.productId || item.rowKey || `${item.sheetName || ''}-${item.productName || ''}-${index}`,
|
||
item,
|
||
tab: 'band',
|
||
bandKeyword: '',
|
||
feeKeyword: '',
|
||
postKeyword: '',
|
||
bandPageNo: 1,
|
||
feePageNo: 1,
|
||
postPageNo: 1
|
||
}))
|
||
}, { immediate: true })
|
||
|
||
function filteredBands(pane) {
|
||
return filterRows(pane.item.weightBands || [], pane.bandKeyword, ['sectionName', 'countryName', 'countryCode', 'zoneName', 'weightLabel', 'chargeName', 'priceUnit'])
|
||
}
|
||
|
||
function filteredFees(pane) {
|
||
return filterRows(pane.item.surcharges || [], pane.feeKeyword, ['feeName', 'zoneName', 'chargeText', 'amount', 'currency', 'priceUnit', 'feeDesc', 'calcRule', 'calcParam', 'postcodeParam'])
|
||
}
|
||
|
||
function addFee(pane) {
|
||
if (!pane.item.surcharges) pane.item.surcharges = []
|
||
const currency = String(pane.item.currency || 'EUR').trim().toUpperCase() || 'EUR'
|
||
const row = {
|
||
feeName: '',
|
||
scId: undefined,
|
||
zoneName: '',
|
||
chargeText: '',
|
||
amount: '',
|
||
priceUnit: currency + '/ITEM',
|
||
currency,
|
||
feeDesc: '',
|
||
calcRule: 'MANUAL',
|
||
calcParam: ''
|
||
}
|
||
pane.item.surcharges.push(row)
|
||
startEdit(row)
|
||
pane.feePageNo = Math.ceil(pane.item.surcharges.length / pageSize) || 1
|
||
}
|
||
|
||
function removeRow(list, row) {
|
||
const index = list.indexOf(row)
|
||
if (index >= 0) list.splice(index, 1)
|
||
}
|
||
|
||
function pageBands(pane) {
|
||
return pageOf(filteredBands(pane), pane.bandPageNo)
|
||
}
|
||
|
||
function pageFees(pane) {
|
||
return pageOf(filteredFees(pane), pane.feePageNo)
|
||
}
|
||
|
||
function postcodeLines(pane) {
|
||
const related = pane.item.relatedSheet || ''
|
||
if (!related.includes('邮编')) return []
|
||
return (pane.item.extraLines || []).filter(line => line.lineType === '邮编')
|
||
}
|
||
|
||
function postcodeTitles(pane) {
|
||
const sheet = `${pane.item.sheetName || ''}${pane.item.productName || ''}`
|
||
if (sheet.includes('DE-iMile')) return ['国家', '产品', '邮编', '状态']
|
||
return ['州/地区', '城市', '地区代码', '邮编', '分区']
|
||
}
|
||
|
||
function postcodeHint(pane) {
|
||
const sheet = `${pane.item.sheetName || ''}${pane.item.productName || ''}`
|
||
if (sheet.includes('DE-iMile')) {
|
||
return '德国这块没有州和城市。状态为 Active 的邮编属于价卡里的德国邮编清单,走同一套重量价格。'
|
||
}
|
||
return '用邮编对应州和城市。分区已换成价卡上的列:city(dense) 为 City,province(no dense) 为 Province,rural(remote) 为 Rural。'
|
||
}
|
||
|
||
function filteredPostcodes(pane) {
|
||
const text = (pane.postKeyword || '').trim().toLowerCase()
|
||
const rows = postcodeLines(pane).map(line => ({ parts: String(line.lineText || '').split(' | '), raw: line.lineText || '' }))
|
||
if (!text) return rows
|
||
return rows.filter(row => row.raw.toLowerCase().includes(text))
|
||
}
|
||
|
||
function pagePostcodes(pane) {
|
||
return pageOf(filteredPostcodes(pane), pane.postPageNo)
|
||
}
|
||
|
||
function filterRows(rows, keyword, fields) {
|
||
const text = (keyword || '').trim().toLowerCase()
|
||
if (!text) return rows
|
||
return rows.filter(row => fields.some(field => String(row?.[field] ?? '').toLowerCase().includes(text)))
|
||
}
|
||
|
||
function pageOf(rows, pageNo) {
|
||
const start = (pageNo - 1) * pageSize
|
||
return rows.slice(start, start + pageSize)
|
||
}
|
||
|
||
function money(value) {
|
||
if (value === null || value === undefined || value === '') return '-'
|
||
const num = Number(value)
|
||
return Number.isNaN(num) ? value : String(num)
|
||
}
|
||
|
||
/** 价格和货币已经拆开时,收费标准只留说明,不再重复「0.19 EUR/ITEM」。 */
|
||
function feeNote(row) {
|
||
const text = (row.chargeText || '').trim()
|
||
if (!text || row.amount === null || row.amount === undefined || row.amount === '') return text
|
||
const amount = Number(row.amount)
|
||
if (!Number.isFinite(amount)) return text
|
||
const match = text.match(/^\s*(\d+(?:\.\d+)?)\s*(?:EUR|GBP|USD|RMB|CNY|欧元|英镑|人民币|美元)?\s*(?:\/\s*[A-Za-z\u4e00-\u9fa5]+)?\s*([\s\S]*)$/i)
|
||
if (!match || Number(match[1]) !== amount) return text
|
||
const rest = (match[2] || '').trim()
|
||
if (!rest || rest === '%' || rest === (row.priceUnit || '') || /^[。..、,,]+$/.test(rest)) return ''
|
||
return rest
|
||
}
|
||
|
||
function periodLabel(row) {
|
||
if (row.effectiveStart || row.effectiveEnd) {
|
||
return `${row.effectiveStart || '…'} 至 ${row.effectiveEnd || '…'}`
|
||
}
|
||
return row.periodText || row.chargeTiming || '-'
|
||
}
|
||
|
||
function statusText(status) {
|
||
return { OK: '已识别', EMPTY: '未识别到配置', MISSING: '缺少工作表' }[status] || status || '-'
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.pay-product + .pay-product {
|
||
margin-top: 18px;
|
||
padding-top: 12px;
|
||
border-top: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
.pay-product-title {
|
||
margin-bottom: 8px;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
}
|
||
.pay-summary {
|
||
margin-bottom: 12px;
|
||
}
|
||
.pay-filter {
|
||
width: 320px;
|
||
}
|
||
.pay-edit-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 8px;
|
||
}
|
||
.pay-hint {
|
||
margin: 0 0 8px;
|
||
color: var(--el-text-color-secondary);
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
}
|
||
.pay-page {
|
||
margin-top: 8px;
|
||
justify-content: flex-end;
|
||
}
|
||
.pay-cond-hint {
|
||
line-height: 1.5;
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
.limit-tag {
|
||
margin-right: 6px;
|
||
}
|
||
</style>
|