套餐加分区功能

This commit is contained in:
wuweihua
2026-09-15 14:10:44 +08:00
parent 76208604bb
commit 7679a7d091
26 changed files with 1279 additions and 597 deletions
@@ -1,7 +1,7 @@
<template>
<el-drawer v-model="visible" size="920px" :title="title" append-to-body>
<el-drawer v-model="visible" size="80%" :title="title" append-to-body>
<div class="mb8" style="display: flex; align-items: center; gap: 12px">
<el-input v-model="keyword" placeholder="搜索国家(中文名 / ISO2)" clearable style="width: 260px" />
<el-input v-model="keyword" placeholder="搜索国家/分区(中文名 / ISO2 / 分区)" clearable style="width: 260px" />
</div>
<simple-table
:data="filteredList"
@@ -12,6 +12,9 @@
empty-text="暂无国家明细"
:span-method="spanMethod"
>
<template #col-countryName="{ row }">
<span class="pre-wrap">{{ row.countryName }} ({{ row.countryCode }})</span>
</template>
<template #col-sizeLimit="{ row }">
<span class="pre-wrap">{{ row.sizeLimit }}</span>
</template>
@@ -28,8 +31,9 @@ import { listProductCountries } from '@/api/price/product'
import { formatWeightLabel } from '@/views/price/set/utils/priceSet'
const columns = [
{ prop: 'countryName', label: '国家', width: 90, showOverflowTooltip: true },
{ prop: 'countryCode', label: 'ISO2', width: 60, align: 'center' },
{ prop: 'countryName', label: '国家', width: 120, showOverflowTooltip: true },
// { prop: 'countryCode', label: 'ISO2', width: 60, align: 'center' },
{ prop: 'zoneName', label: '分区名称', width: 140, showOverflowTooltip: true, formatter: (row) => row.zoneName || '-' },
{
prop: 'weightMin',
label: '重量段(KG)',
@@ -61,7 +65,9 @@ const filteredList = computed(() => {
const st = status.value
return list.value.filter(r => {
if (kw && !(String(r.countryName || '').toLowerCase().includes(kw) ||
String(r.countryCode || '').toLowerCase().includes(kw))) return false
String(r.countryCode || '').toLowerCase().includes(kw) ||
String(r.zoneCode || '').toLowerCase().includes(kw) ||
String(r.zoneName || '').toLowerCase().includes(kw))) return false
if (st && String(r.status) !== String(st)) return false
return true
})
@@ -81,23 +87,29 @@ function open(row) {
status.value = ''
listProductCountries(row.productCode, formatEffDate(row.effectiveDate), row.productId).then(res => {
list.value = (res.data || []).slice().sort((a, b) => {
return String(a.countryCode || '').localeCompare(String(b.countryCode || ''))
const c = String(a.countryCode || '').localeCompare(String(b.countryCode || ''))
if (c !== 0) return c
return String(a.zoneCode || '').localeCompare(String(b.zoneCode || ''))
})
}).finally(() => {
loading.value = false
})
}
function rowGroupKey(row) {
return String(row?.countryCode || '') + '|' + String(row?.zoneCode || '') + '|' + String(row?.countryName || '')
}
function spanMethod({ column, rowIndex }) {
if (column.property !== 'countryName' && column.property !== 'countryCode') return
if (!['countryName', 'countryCode', 'zoneCode', 'zoneName', 'zoneId'].includes(column.property)) return
const rows = filteredList.value
if (!rows.length) return
const cur = rows[rowIndex]?.countryCode
if (rowIndex > 0 && rows[rowIndex - 1]?.countryCode === cur) {
const cur = rowGroupKey(rows[rowIndex])
if (rowIndex > 0 && rowGroupKey(rows[rowIndex - 1]) === cur) {
return { rowspan: 0, colspan: 0 }
}
let count = 1
while (rowIndex + count < rows.length && rows[rowIndex + count]?.countryCode === cur) count++
while (rowIndex + count < rows.length && rowGroupKey(rows[rowIndex + count]) === cur) count++
return { rowspan: count, colspan: 1 }
}
@@ -0,0 +1,135 @@
<template>
<el-drawer v-model="visible" size="720px" append-to-body>
<template #header>
<div class="drawer-head">
<span class="drawer-title">邮编明细</span>
<span v-if="selectedZone" class="drawer-sub">
{{ selectedZone.zoneName || selectedZone.zoneCode }}
({{ detailTotal }}条)
</span>
</div>
</template>
<div class="drawer-toolbar">
<el-input
v-model="query.postcode"
clearable
placeholder="搜索邮编"
style="width: 200px; margin-right: 8px"
@keyup.enter="loadList"
/>
<el-button type="primary" plain icon="Search" size="small" @click="loadList">搜索</el-button>
<el-button type="primary" plain icon="Plus" size="small" @click="handleAdd" v-hasPermi="['price:zone:add']">新增邮编</el-button>
</div>
<el-table v-loading="loading" :data="list" border row-key="detailId" empty-text="暂无邮编">
<el-table-column label="ID" prop="detailId" align="center" width="90" />
<el-table-column label="邮编" prop="postcode" align="center" min-width="140" />
<el-table-column label="操作" align="center" width="160">
<template #default="{ row }">
<el-button link type="primary" icon="Edit" @click="handleUpdate(row)" v-hasPermi="['price:zone:edit']">编辑</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(row.detailId)" v-hasPermi="['price:zone:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="drawer-pager">
<el-pagination
v-model:current-page="query.pageNum"
v-model:page-size="query.pageSize"
:total="detailTotal"
:page-sizes="[20, 50, 100, 200]"
layout="total, sizes, prev, pager, next"
@size-change="loadList"
@current-change="loadList"
/>
</div>
<postcode-form-dialog ref="postcodeFormRef" @success="onChanged" />
</el-drawer>
</template>
<script setup name="PostcodeDrawer">
import { listDetail, delDetail } from '@/api/price/zone'
import PostcodeFormDialog from './PostcodeFormDialog.vue'
const { proxy } = getCurrentInstance()
const emit = defineEmits(['changed'])
const visible = ref(false)
const selectedZone = ref(null)
const list = ref([])
const loading = ref(false)
const detailTotal = ref(0)
const query = ref({ pageNum: 1, pageSize: 50, postcode: undefined })
const postcodeFormRef = ref()
function open(zone) {
selectedZone.value = zone
query.value = { pageNum: 1, pageSize: 50, postcode: undefined }
visible.value = true
loadList()
}
function loadList() {
if (!selectedZone.value?.zoneId) {
list.value = []
detailTotal.value = 0
return
}
loading.value = true
listDetail({
zoneId: selectedZone.value.zoneId,
postcode: query.value.postcode || undefined,
pageNum: query.value.pageNum,
pageSize: query.value.pageSize
}).then(res => {
list.value = res.rows || []
detailTotal.value = res.total || 0
}).catch(() => {
list.value = []
detailTotal.value = 0
}).finally(() => {
loading.value = false
})
}
function handleAdd() {
postcodeFormRef.value.open(selectedZone.value)
}
function handleUpdate(row) {
postcodeFormRef.value.open(selectedZone.value, row)
}
/** 邮编增删改后:刷新本列表,并向上通知(分区表需更新邮编数) */
function onChanged() {
loadList()
emit('changed')
}
function handleDelete(detailIds) {
proxy.$modal.confirm('是否确认删除邮编编号为"' + detailIds + '"的数据?').then(() => {
return delDetail(detailIds)
}).then(() => {
proxy.$modal.msgSuccess('删除成功')
onChanged()
}).catch(() => {})
}
defineExpose({ open })
</script>
<style scoped>
.drawer-head {
display: flex;
align-items: center;
gap: 10px;
}
.drawer-title { font-size: 16px; font-weight: 600; }
.drawer-sub { color: #909399; font-size: 13px; }
.drawer-toolbar { margin-bottom: 12px; display: flex; align-items: center; flex-wrap: wrap; gap: 4px; }
.drawer-pager { margin-top: 16px; display: flex; justify-content: flex-end; }
</style>
@@ -0,0 +1,63 @@
<template>
<el-dialog :title="title" v-model="visible" width="420px" append-to-body>
<el-form ref="postcodeFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="邮编" prop="postcode">
<el-input v-model="form.postcode" placeholder="请输入邮编" maxlength="32" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="visible = false">取 消</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup name="PostcodeFormDialog">
import { addDetail, updateDetail } from '@/api/price/zone'
const { proxy } = getCurrentInstance()
const emit = defineEmits(['success'])
const visible = ref(false)
const title = ref('')
const postcodeFormRef = ref()
const form = ref({})
const rules = {
postcode: [{ required: true, message: '邮编不能为空', trigger: 'blur' }]
}
/** zone 为所属分区;row 传空为新增,传邮编行为编辑 */
function open(zone, row) {
form.value = row
? { ...row }
: {
detailId: undefined,
zoneId: zone?.zoneId,
postcode: undefined
}
title.value = row ? '编辑邮编' : '新增邮编'
visible.value = true
nextTick(() => postcodeFormRef.value?.clearValidate())
}
function submitForm() {
postcodeFormRef.value.validate(valid => {
if (!valid) return
const payload = {
...form.value,
postcode: String(form.value.postcode || '').trim()
}
const req = payload.detailId != null ? updateDetail(payload) : addDetail(payload)
req.then(() => {
proxy.$modal.msgSuccess(payload.detailId != null ? '修改成功' : '新增成功')
visible.value = false
emit('success')
})
})
}
defineExpose({ open })
</script>
@@ -0,0 +1,222 @@
<template>
<el-drawer v-model="visible" size="860px" append-to-body>
<template #header>
<div class="drawer-head">
<span class="drawer-title">国家分区</span>
<el-select
v-model="selectedRegionId"
filterable
clearable
placeholder="选择国家"
class="country-select"
@change="loadZoneList"
>
<el-option
v-for="r in regionOptions"
:key="r.regionId"
:label="regionLabel(r)"
:value="r.regionId"
/>
</el-select>
<span v-if="selectedCountry" class="drawer-sub">
当前:{{ selectedCountry.countryNameCn }}
<template v-if="selectedCountry.countryCode2">({{ selectedCountry.countryCode2 }})</template>
,共 {{ zoneList.length }} 个分区
</span>
</div>
</template>
<div v-loading="zoneLoading">
<div class="zone-toolbar">
<el-button
type="primary"
plain
icon="Plus"
size="small"
:disabled="!selectedCountry"
@click="handleZoneAdd"
v-hasPermi="['price:zone:add']"
>新增分区</el-button>
<el-button
type="danger"
plain
icon="Delete"
size="small"
:disabled="!selectedCountry"
@click="handleClearZones"
v-hasPermi="['price:zone:remove']"
>清空分区</el-button>
</div>
<el-table
:data="zoneList"
border
highlight-current-row
row-key="zoneId"
:empty-text="selectedCountry ? '该国家暂无分区,点击「新增分区」创建' : '请先选择国家'"
>
<el-table-column label="国家" align="center" min-width="180">
<template #default="{ row }">
<div class="zone-country-cell">
<div class="zone-title">{{ row.zoneName || ((selectedCountry?.countryNameCn || '') + '(' + row.zoneCode + ')') }}</div>
<el-button link type="primary" class="zone-click" @click="openPostcodeDrawer(row)" v-hasPermi="['price:zone:query']">分区点击</el-button>
</div>
</template>
</el-table-column>
<el-table-column label="国家编码" prop="countryCode" align="center" width="110" />
<el-table-column label="分区代码" prop="zoneCode" align="center" width="120" />
<el-table-column label="邮编数" prop="detailCount" align="center" width="100" />
<el-table-column label="排序" prop="zoneSort" align="center" width="80" />
<el-table-column label="操作" align="center" width="220" fixed="right">
<template #default="{ row }">
<el-button link type="primary" icon="View" @click="openPostcodeDrawer(row)" v-hasPermi="['price:zone:query']">明细</el-button>
<el-button link type="primary" icon="Edit" @click="handleZoneUpdate(row)" v-hasPermi="['price:zone:edit']">编辑</el-button>
<el-button link type="primary" icon="Delete" @click="handleZoneDelete(row.zoneId)" v-hasPermi="['price:zone:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<zone-form-dialog ref="zoneFormRef" :country="selectedCountry" @success="onZoneChanged" />
<postcode-drawer ref="postcodeDrawerRef" @changed="onZoneChanged" />
</el-drawer>
</template>
<script setup name="ZoneDrawer">
import { listRegionOptions } from '@/api/data/region'
import {
delCountryZones,
listZone, addZone, updateZone, delZone
} from '@/api/price/zone'
import ZoneFormDialog from './ZoneFormDialog.vue'
import PostcodeDrawer from './PostcodeDrawer.vue'
const { proxy } = getCurrentInstance()
const emit = defineEmits(['changed'])
const visible = ref(false)
const selectedRegionId = ref(null)
const selectedCountry = computed(() =>
regionOptions.value.find(r => r.regionId === selectedRegionId.value) || null
)
const zoneList = ref([])
const zoneLoading = ref(false)
const regionOptions = ref([])
const zoneFormRef = ref()
const postcodeDrawerRef = ref()
function regionLabel(r) {
const cn = r.countryNameCn || r.countryNameEn || ''
return cn ? `${r.flagEmoji || ''} ${cn} (${r.countryCode2})`.trim() : r.countryCode2
}
function ensureRegions() {
if (regionOptions.value.length) return Promise.resolve()
return listRegionOptions().then(res => {
regionOptions.value = res.data || []
})
}
/** 打开抽屉;传入国家行则自动选中,不传则让用户在头部下拉选择 */
function open(country) {
visible.value = true
ensureRegions().then(() => {
selectedRegionId.value = country?.regionId || null
loadZoneList(selectedRegionId.value)
})
}
function reload() {
loadZoneList(selectedRegionId.value)
}
function loadZoneList(regionId) {
if (!regionId) {
zoneList.value = []
return
}
zoneLoading.value = true
listZone({ regionId, pageNum: 1, pageSize: 200 }).then(res => {
zoneList.value = res.rows || []
}).catch(() => {
zoneList.value = []
}).finally(() => {
zoneLoading.value = false
})
}
function handleZoneAdd() {
zoneFormRef.value.open()
}
function handleZoneUpdate(row) {
zoneFormRef.value.open(row)
}
/** 分区增删改 / 邮编变动后:刷新分区列表,并通知父页刷新国家表格统计 */
function onZoneChanged() {
loadZoneList(selectedRegionId.value)
emit('changed')
}
function handleZoneDelete(zoneIds) {
proxy.$modal.confirm('是否确认删除分区编号为"' + zoneIds + '"的数据?将同时删除邮编明细。').then(() => {
return delZone(zoneIds)
}).then(() => {
proxy.$modal.msgSuccess('删除成功')
onZoneChanged()
}).catch(() => {})
}
function handleClearZones() {
if (!selectedRegionId.value) return
proxy.$modal.confirm('是否清空国家 regionId="' + selectedRegionId.value + '" 下的全部分区与邮编?(不会删除 bms_region 国家主数据)').then(() => {
return delCountryZones(selectedRegionId.value)
}).then(() => {
zoneList.value = []
proxy.$modal.msgSuccess('清空成功')
emit('changed')
}).catch(() => {})
}
/** 抽屉打开期间,外部清空了某国家(国家表格「清空」按钮)时同步列表 */
function onCountryCleared(regionIds) {
if (visible.value && String(regionIds).split(',').map(String).includes(String(selectedRegionId.value))) {
zoneList.value = []
}
}
function openPostcodeDrawer(row) {
postcodeDrawerRef.value.open(row)
}
defineExpose({ open, reload, onCountryCleared })
</script>
<style scoped>
.drawer-head {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.drawer-title { font-size: 16px; font-weight: 600; }
.drawer-sub { color: #909399; font-size: 13px; }
.country-select { width: 260px; }
.zone-toolbar {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.zone-country-cell {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
padding: 4px 0;
}
.zone-title { font-weight: 500; }
.zone-click { padding: 0; height: auto; font-size: 12px; }
</style>
@@ -0,0 +1,100 @@
<template>
<el-dialog :title="title" v-model="open" width="520px" append-to-body>
<el-form ref="zoneFormRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="国家">
<span>{{ country.countryNameCn }} ({{ country.countryCode2 }})</span>
</el-form-item>
<el-form-item label="国家编码" prop="countryCode">
<el-input v-model="form.countryCode" placeholder="ISO2,如 AU" maxlength="10" style="text-transform: uppercase" />
</el-form-item>
<el-form-item label="分区代码" prop="zoneCode">
<el-input v-model="form.zoneCode" placeholder="如 1区" maxlength="50" />
</el-form-item>
<el-form-item label="显示名" prop="zoneName">
<el-input v-model="form.zoneName" placeholder="如 澳大利亚(1区)" maxlength="100" />
</el-form-item>
<el-form-item label="排序号" prop="zoneSort">
<el-input-number v-model="form.zoneSort" :min="0" :max="9999" controls-position="right" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="open = false">取 消</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup name="ZoneFormDialog">
import { addZone, updateZone } from '@/api/price/zone'
const { proxy } = getCurrentInstance()
const props = defineProps({
/** 当前抽屉选中的国家(bms_region 行) */
country: {
type: Object,
default: null
}
})
const emit = defineEmits(['success'])
const open = ref(false)
const title = ref('')
const zoneFormRef = ref()
const form = ref({})
const rules = {
countryCode: [{ required: true, message: '国家编码不能为空', trigger: 'blur' }],
zoneCode: [{ required: true, message: '分区代码不能为空', trigger: 'blur' }]
}
/** row 传空为新增,传分区行为编辑 */
function open2(row) {
form.value = row
? { ...row }
: {
zoneId: undefined,
regionId: props.country?.regionId,
countryCode: props.country?.countryCode2 || undefined,
zoneCode: undefined,
zoneName: undefined,
zoneSort: 0,
remark: undefined
}
title.value = row ? '编辑分区' : '新增分区'
open.value = true
nextTick(() => zoneFormRef.value?.clearValidate())
}
function submitForm() {
zoneFormRef.value.validate(valid => {
if (!valid) return
const region = props.country
const cn = region?.countryNameCn || ''
const code = (form.value.countryCode || region?.countryCode2 || '')
.toString().trim().toUpperCase()
const payload = {
...form.value,
regionId: region?.regionId,
countryCode: code,
zoneName: form.value.zoneName || (cn ? (cn + '(' + form.value.zoneCode + ')') : form.value.zoneCode)
}
delete payload.detailCount
delete payload.countryNameCn
delete payload.countryCode2
const req = payload.zoneId != null ? updateZone(payload) : addZone(payload)
req.then(() => {
proxy.$modal.msgSuccess(payload.zoneId != null ? '修改成功' : '新增成功')
open.value = false
emit('success')
})
})
}
defineExpose({ open: open2 })
</script>
+18 -439
View File
@@ -5,7 +5,7 @@
:closable="false"
show-icon
class="mb16"
title="国家来自 bms_region。支持导入:澳大利亚分区、加拿大偏远附加费、法国MR邮编、日本附加费、英国附加费。"
title="国家来自 bms_region;点击「分区」在抽屉中查看/维护国家分区,抽屉内点击「分区点击」查看邮编明细。导入 Excel 时需 sheet 名以「分区」结尾且能匹配国家表。"
/>
<basic-table
@@ -14,14 +14,10 @@
:search-columns="countrySearchColumns"
:query="queryCountryList"
row-key="regionId"
@row-click="handleCountryRowClick"
>
<template #toolbar="{ selection, ids }">
<template #toolbar>
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handlePickRegionAddZone" v-hasPermi="['price:zone:add']">为国家新增分区</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="!selection.length" @click="handleCountryDelete(ids)" v-hasPermi="['price:zone:remove']">清空分区</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAddZone" v-hasPermi="['price:zone:add']">为国家新增分区</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Upload" @click="handleImport" v-hasPermi="['price:zone:add']">导入 Excel</el-button>
@@ -35,169 +31,12 @@
</template>
<template #col-action="{ row }">
<el-button link type="primary" icon="List" @click.stop="handleCountryRowClick(row)" v-hasPermi="['price:zone:query']">分区</el-button>
<el-button link type="primary" icon="List" @click.stop="openZoneDrawer(row)" v-hasPermi="['price:zone:query']">分区</el-button>
<el-button link type="primary" icon="Delete" @click.stop="handleCountryDelete(row.regionId)" v-hasPermi="['price:zone:remove']">清空</el-button>
</template>
</basic-table>
<el-card shadow="never" class="detail-card" v-loading="zoneLoading">
<template #header>
<div class="card-header">
<span>
国家分区
<span v-if="selectedCountry" class="sub-hint">
当前:{{ selectedCountry.countryNameCn }}
<template v-if="selectedCountry.countryCode2">({{ selectedCountry.countryCode2 }})</template>
,共 {{ zoneList.length }} 个分区
</span>
<span v-else class="sub-hint">请点击上方国家行查看分区</span>
</span>
<el-button
type="primary"
plain
icon="Plus"
size="small"
:disabled="!selectedCountry"
@click="handleZoneAdd"
v-hasPermi="['price:zone:add']"
>新增分区</el-button>
</div>
</template>
<el-table
:data="zoneList"
border
highlight-current-row
row-key="zoneId"
empty-text="暂无分区数据"
>
<el-table-column label="国家" align="center" min-width="180">
<template #default="{ row }">
<div class="zone-country-cell">
<div class="zone-title">{{ row.zoneName || ((selectedCountry?.countryNameCn || '') + '(' + row.zoneCode + ')') }}</div>
<el-button link type="primary" class="zone-click" @click="openDetailDrawer(row)" v-hasPermi="['price:zone:query']">分区点击</el-button>
</div>
</template>
</el-table-column>
<el-table-column label="国家编码" prop="countryCode" align="center" width="110" />
<el-table-column label="分区代码" prop="zoneCode" align="center" width="120" />
<el-table-column label="邮编数" prop="detailCount" align="center" width="100" />
<el-table-column label="排序" prop="zoneSort" align="center" width="80" />
<el-table-column label="操作" align="center" width="220" fixed="right">
<template #default="{ row }">
<el-button link type="primary" icon="View" @click="openDetailDrawer(row)" v-hasPermi="['price:zone:query']">明细</el-button>
<el-button link type="primary" icon="Edit" @click="handleZoneUpdate(row)" v-hasPermi="['price:zone:edit']">编辑</el-button>
<el-button link type="primary" icon="Delete" @click="handleZoneDelete(row.zoneId)" v-hasPermi="['price:zone:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog :title="zoneTitle" v-model="zoneOpen" width="520px" append-to-body>
<el-form ref="zoneFormRef" :model="zoneForm" :rules="zoneRules" label-width="100px">
<el-form-item v-if="!selectedCountry || zoneForm._pickRegion" label="国家" prop="regionId">
<el-select
v-model="zoneForm.regionId"
filterable
clearable
placeholder="从 bms_region 选择国家"
style="width: 100%"
@change="onZoneRegionChange"
>
<el-option
v-for="r in regionOptions"
:key="r.regionId"
:label="regionLabel(r)"
:value="r.regionId"
/>
</el-select>
</el-form-item>
<el-form-item v-else label="国家">
<span>{{ selectedCountry.countryNameCn }} ({{ selectedCountry.countryCode2 }})</span>
</el-form-item>
<el-form-item label="国家编码" prop="countryCode">
<el-input v-model="zoneForm.countryCode" disabled placeholder="ISO2,如 AU" maxlength="10" style="text-transform: uppercase" />
</el-form-item>
<el-form-item label="分区代码" prop="zoneCode">
<el-input v-model="zoneForm.zoneCode" placeholder="如 Z1" maxlength="50" />
</el-form-item>
<el-form-item label="显示名" prop="zoneName">
<el-input v-model="zoneForm.zoneName" placeholder="如 澳大利亚(Z1)" maxlength="100" />
</el-form-item>
<el-form-item label="排序号" prop="zoneSort">
<el-input-number v-model="zoneForm.zoneSort" :min="0" :max="9999" controls-position="right" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="zoneForm.remark" type="textarea" :rows="3" placeholder="备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitZoneForm">确 定</el-button>
<el-button @click="zoneOpen = false">取 消</el-button>
</div>
</template>
</el-dialog>
<el-drawer v-model="detailDrawerVisible" size="720px" append-to-body>
<template #header>
<div class="drawer-head">
<span class="drawer-title">邮编明细</span>
<span v-if="selectedZone" class="drawer-sub">
{{ selectedZone.zoneName || selectedZone.zoneCode }}
({{ detailTotal }}条)
</span>
</div>
</template>
<div class="drawer-toolbar">
<el-input
v-model="detailQuery.postcode"
clearable
placeholder="搜索邮编"
style="width: 200px; margin-right: 8px"
@keyup.enter="loadDetailList"
/>
<el-button type="primary" plain icon="Search" size="small" @click="loadDetailList">搜索</el-button>
<el-button type="primary" plain icon="Plus" size="small" @click="handleDetailAdd" v-hasPermi="['price:zone:add']">新增邮编</el-button>
</div>
<el-table v-loading="detailLoading" :data="detailList" border row-key="detailId" empty-text="暂无邮编">
<el-table-column label="邮编" prop="postcode" align="center" min-width="140" />
<el-table-column label="操作" align="center" width="160">
<template #default="{ row }">
<el-button link type="primary" icon="Edit" @click="handleDetailUpdate(row)" v-hasPermi="['price:zone:edit']">编辑</el-button>
<el-button link type="primary" icon="Delete" @click="handleDetailDelete(row.detailId)" v-hasPermi="['price:zone:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="drawer-pager">
<el-pagination
v-model:current-page="detailQuery.pageNum"
v-model:page-size="detailQuery.pageSize"
:total="detailTotal"
:page-sizes="[20, 50, 100, 200]"
layout="total, sizes, prev, pager, next"
@size-change="loadDetailList"
@current-change="loadDetailList"
/>
</div>
</el-drawer>
<el-dialog :title="detailTitle" v-model="detailOpen" width="420px" append-to-body>
<el-form ref="detailFormRef" :model="detailForm" :rules="detailRules" label-width="80px">
<el-form-item label="邮编" prop="postcode">
<el-input v-model="detailForm.postcode" placeholder="请输入邮编" maxlength="32" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitDetailForm">确 定</el-button>
<el-button @click="detailOpen = false">取 消</el-button>
</div>
</template>
</el-dialog>
<zone-drawer ref="zoneDrawerRef" @changed="onZoneChanged" />
<excel-import-dialog
ref="importRef"
@@ -212,45 +51,13 @@
<script setup name="PriceZone">
import ExcelImportDialog from '@/components/ExcelImportDialog'
import { listRegionOptions } from '@/api/data/region'
import {
listCountry, delCountryZones,
listZone, addZone, updateZone, delZone,
listDetail, addDetail, updateDetail, delDetail
} from '@/api/price/zone'
import { listCountry, delCountryZones } from '@/api/price/zone'
import ZoneDrawer from './components/ZoneDrawer.vue'
const { proxy } = getCurrentInstance()
const countryTableRef = ref()
const selectedCountry = ref(null)
const zoneList = ref([])
const zoneLoading = ref(false)
const regionOptions = ref([])
const zoneOpen = ref(false)
const zoneTitle = ref('')
const zoneFormRef = ref()
const zoneForm = ref({})
const zoneRules = {
regionId: [{ required: true, message: '请选择国家', trigger: 'change' }],
countryCode: [{ required: true, message: '国家编码不能为空', trigger: 'blur' }],
zoneCode: [{ required: true, message: '分区代码不能为空', trigger: 'blur' }]
}
const detailDrawerVisible = ref(false)
const selectedZone = ref(null)
const detailList = ref([])
const detailLoading = ref(false)
const detailTotal = ref(0)
const detailQuery = ref({ pageNum: 1, pageSize: 50, postcode: undefined })
const detailOpen = ref(false)
const detailTitle = ref('')
const detailFormRef = ref()
const detailForm = ref({})
const detailRules = {
postcode: [{ required: true, message: '邮编不能为空', trigger: 'blur' }]
}
const zoneDrawerRef = ref()
const importRef = ref()
const countrySearchColumns = [
@@ -272,42 +79,18 @@ function queryCountryList(params) {
return listCountry(params)
}
function regionLabel(r) {
const cn = r.countryNameCn || r.countryNameEn || ''
return cn ? `${r.flagEmoji || ''} ${cn} (${r.countryCode2})`.trim() : r.countryCode2
/** 工具栏「为国家新增分区」:不传国家,抽屉内自行选择 */
function handleAddZone() {
zoneDrawerRef.value.open()
}
function ensureRegions() {
if (regionOptions.value.length) return Promise.resolve()
return listRegionOptions().then(res => {
regionOptions.value = res.data || []
})
function openZoneDrawer(row) {
zoneDrawerRef.value.open(row)
}
function onZoneRegionChange(regionId) {
const r = regionOptions.value.find(item => item.regionId === regionId)
zoneForm.value.countryCode = r?.countryCode2 ? String(r.countryCode2).toUpperCase() : undefined
}
function handleCountryRowClick(row) {
if (!row?.regionId) return
selectedCountry.value = row
loadZoneList(row.regionId)
}
function loadZoneList(regionId) {
if (!regionId) {
zoneList.value = []
return
}
zoneLoading.value = true
listZone({ regionId, pageNum: 1, pageSize: 200 }).then(res => {
zoneList.value = res.rows || []
}).catch(() => {
zoneList.value = []
}).finally(() => {
zoneLoading.value = false
})
/** 抽屉内分区/邮编有变动 → 刷新国家表格的分区数/邮编数统计 */
function onZoneChanged() {
countryTableRef.value.refresh()
}
function handleCountryDelete(regionIds) {
@@ -315,225 +98,21 @@ function handleCountryDelete(regionIds) {
return delCountryZones(regionIds)
}).then(() => {
countryTableRef.value.refresh()
if (selectedCountry.value && String(regionIds).split(',').map(String).includes(String(selectedCountry.value.regionId))) {
selectedCountry.value = null
zoneList.value = []
}
zoneDrawerRef.value.onCountryCleared(regionIds)
proxy.$modal.msgSuccess('清空成功')
}).catch(() => {})
}
function resetZoneForm(pickRegion) {
zoneForm.value = {
zoneId: undefined,
regionId: pickRegion ? undefined : selectedCountry.value?.regionId,
countryCode: pickRegion ? undefined : (selectedCountry.value?.countryCode2 || undefined),
zoneCode: undefined,
zoneName: undefined,
zoneSort: 0,
remark: undefined,
_pickRegion: !!pickRegion
}
proxy.resetForm('zoneFormRef')
}
function handlePickRegionAddZone() {
ensureRegions().then(() => {
resetZoneForm(true)
zoneOpen.value = true
zoneTitle.value = '为国家新增分区'
})
}
function handleZoneAdd() {
if (!selectedCountry.value) {
handlePickRegionAddZone()
return
}
resetZoneForm(false)
zoneOpen.value = true
zoneTitle.value = '新增分区'
}
function handleZoneUpdate(row) {
resetZoneForm(false)
zoneForm.value = { ...row, _pickRegion: false }
zoneOpen.value = true
zoneTitle.value = '编辑分区'
}
function submitZoneForm() {
proxy.$refs['zoneFormRef'].validate(valid => {
if (!valid) return
const regionId = zoneForm.value.regionId || selectedCountry.value?.regionId
const region = regionOptions.value.find(r => r.regionId === regionId)
|| selectedCountry.value
const cn = region?.countryNameCn || ''
const code = (zoneForm.value.countryCode || region?.countryCode2 || selectedCountry.value?.countryCode2 || '')
.toString().trim().toUpperCase()
const payload = {
...zoneForm.value,
regionId,
countryCode: code,
zoneName: zoneForm.value.zoneName || (cn ? (cn + '(' + zoneForm.value.zoneCode + ')') : zoneForm.value.zoneCode)
}
delete payload._pickRegion
delete payload.detailCount
delete payload.countryNameCn
delete payload.countryCode2
const req = payload.zoneId != null ? updateZone(payload) : addZone(payload)
req.then(() => {
proxy.$modal.msgSuccess(payload.zoneId != null ? '修改成功' : '新增成功')
zoneOpen.value = false
countryTableRef.value.refresh()
if (selectedCountry.value?.regionId === regionId) {
loadZoneList(regionId)
} else {
selectedCountry.value = {
regionId,
countryNameCn: cn || region?.countryNameCn,
countryCode2: region?.countryCode2
}
loadZoneList(regionId)
}
})
})
}
function handleZoneDelete(zoneIds) {
proxy.$modal.confirm('是否确认删除分区编号为"' + zoneIds + '"的数据?将同时删除邮编明细。').then(() => {
return delZone(zoneIds)
}).then(() => {
loadZoneList(selectedCountry.value.regionId)
countryTableRef.value.refresh()
proxy.$modal.msgSuccess('删除成功')
}).catch(() => {})
}
function openDetailDrawer(row) {
selectedZone.value = row
detailQuery.value = { pageNum: 1, pageSize: 50, postcode: undefined }
detailDrawerVisible.value = true
loadDetailList()
}
function loadDetailList() {
if (!selectedZone.value?.zoneId) {
detailList.value = []
detailTotal.value = 0
return
}
detailLoading.value = true
listDetail({
zoneId: selectedZone.value.zoneId,
postcode: detailQuery.value.postcode || undefined,
pageNum: detailQuery.value.pageNum,
pageSize: detailQuery.value.pageSize
}).then(res => {
detailList.value = res.rows || []
detailTotal.value = res.total || 0
}).catch(() => {
detailList.value = []
detailTotal.value = 0
}).finally(() => {
detailLoading.value = false
})
}
function resetDetailForm() {
detailForm.value = {
detailId: undefined,
zoneId: selectedZone.value?.zoneId,
postcode: undefined
}
proxy.resetForm('detailFormRef')
}
function handleDetailAdd() {
resetDetailForm()
detailOpen.value = true
detailTitle.value = '新增邮编'
}
function handleDetailUpdate(row) {
resetDetailForm()
detailForm.value = { ...row }
detailOpen.value = true
detailTitle.value = '编辑邮编'
}
function submitDetailForm() {
proxy.$refs['detailFormRef'].validate(valid => {
if (!valid) return
const payload = {
...detailForm.value,
zoneId: selectedZone.value.zoneId,
postcode: String(detailForm.value.postcode || '').trim()
}
const req = payload.detailId != null ? updateDetail(payload) : addDetail(payload)
req.then(() => {
proxy.$modal.msgSuccess(payload.detailId != null ? '修改成功' : '新增成功')
detailOpen.value = false
loadDetailList()
loadZoneList(selectedCountry.value.regionId)
countryTableRef.value.refresh()
})
})
}
function handleDetailDelete(detailIds) {
proxy.$modal.confirm('是否确认删除邮编编号为"' + detailIds + '"的数据?').then(() => {
return delDetail(detailIds)
}).then(() => {
loadDetailList()
loadZoneList(selectedCountry.value.regionId)
countryTableRef.value.refresh()
proxy.$modal.msgSuccess('删除成功')
}).catch(() => {})
}
function handleImport() {
importRef.value.open()
}
function onImportSuccess() {
countryTableRef.value.refresh()
if (selectedCountry.value?.regionId) {
loadZoneList(selectedCountry.value.regionId)
}
zoneDrawerRef.value.reload()
}
</script>
<style scoped>
.mb16 { margin-bottom: 16px; }
.detail-card { margin-top: 16px; }
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.sub-hint {
margin-left: 10px;
color: #909399;
font-size: 13px;
font-weight: normal;
}
.zone-country-cell {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
padding: 4px 0;
}
.zone-title { font-weight: 500; }
.zone-click { padding: 0; height: auto; font-size: 12px; }
.drawer-head {
display: flex;
align-items: center;
gap: 10px;
}
.drawer-title { font-size: 16px; font-weight: 600; }
.drawer-sub { color: #909399; font-size: 13px; }
.drawer-toolbar { margin-bottom: 12px; display: flex; align-items: center; flex-wrap: wrap; gap: 4px; }
.drawer-pager { margin-top: 16px; display: flex; justify-content: flex-end; }
</style>
@@ -16,7 +16,7 @@
:closable="false"
show-icon
class="mb12"
title="FIXED 模式:可不填明细(计费取 bms_product_country 默认运费/挂号费);有明细时按产品×国家×重量段覆盖固定价。附加费来自产品关联,只读展示。"
title="FIXED 模式:可不填明细(计费取 bms_product_country 默认运费/挂号费);有明细时按产品×国家×分区×重量段覆盖固定价。附加费来自产品关联,只读展示。"
/>
<el-alert
v-else
@@ -64,6 +64,29 @@
</template>
</el-table-column>
<el-table-column v-if="isFixedType(setType)" label="分区" min-width="140">
<template #default="{ row }">
<el-select
v-if="refData.hasZonesOf(row)"
v-model="row.zoneCode"
filterable
clearable
placeholder="选择分区"
style="width: 100%"
:disabled="!row._countryKey"
@change="onZoneChange(row)"
>
<el-option
v-for="z in refData.zoneOptionsOf(row)"
:key="z.key"
:label="z.label"
:value="z.zoneCode"
/>
</el-select>
<span v-else class="muted">-</span>
</template>
</el-table-column>
<el-table-column v-if="isFixedType(setType)" label="重量段" min-width="150">
<template #default="{ row }">
<el-select
@@ -72,7 +95,7 @@
clearable
placeholder="选择重量段"
style="width: 100%"
:disabled="!row._countryKey"
:disabled="!row._countryKey || (refData.hasZonesOf(row) && !row.zoneCode)"
@change="onWeightChange(row)"
>
<el-option
@@ -158,7 +181,7 @@ const { bms_round_mode } = useDict('bms_round_mode')
/** 参考数据来自父页面,缓存与请求去重全页面共享 */
const { productOptions, surchargesOf, countryOptionsOf, weightOptionsOf } = props.refData
const { onProductChange, onCountryChange, onWeightChange } = useDetailCascade(props.refData)
const { onProductChange, onCountryChange, onZoneChange, onWeightChange } = useDetailCascade(props.refData)
function addRow() {
props.details.push(createEmptyDetailRow())
@@ -37,6 +37,21 @@
</el-select>
</el-form-item>
</el-col>
<el-col v-if="hasZones" :span="12">
<el-form-item label="分区" prop="zoneCode">
<el-select
v-model="form.zoneCode"
filterable
clearable
placeholder="选择分区"
style="width: 100%"
:disabled="!form._countryKey"
@change="onZoneChange(form)"
>
<el-option v-for="z in zoneOptions" :key="z.key" :label="z.label" :value="z.zoneCode" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="重量段" prop="weightLabel">
<el-select
@@ -45,7 +60,7 @@
clearable
placeholder="选择重量段"
style="width: 100%"
:disabled="!form._countryKey"
:disabled="!form._countryKey || (hasZones && !form.zoneCode)"
@change="onWeightChange(form)"
>
<el-option v-for="w in weightOptions" :key="w.value" :label="w.label" :value="w.value" />
@@ -173,8 +188,8 @@ const emit = defineEmits(['success'])
const { proxy } = getCurrentInstance()
const { bms_round_mode } = useDict('bms_round_mode')
const { productOptions, surchargesOf, countryOptionsOf, weightOptionsOf } = props.refData
const { onProductChange, onCountryChange, onWeightChange } = useDetailCascade(props.refData)
const { productOptions, surchargesOf, countryOptionsOf, zoneOptionsOf, hasZonesOf, weightOptionsOf } = props.refData
const { onProductChange, onCountryChange, onZoneChange, onWeightChange } = useDetailCascade(props.refData)
const visible = ref(false)
const title = ref('')
@@ -189,6 +204,8 @@ const rules = {
const surcharges = computed(() => surchargesOf(form.value))
const countryOptions = computed(() => countryOptionsOf(form.value))
const zoneOptions = computed(() => zoneOptionsOf(form.value))
const hasZones = computed(() => hasZonesOf(form.value))
const weightOptions = computed(() => weightOptionsOf(form.value))
/** 打开弹窗:row 为空表示新增 */
@@ -30,6 +30,9 @@
<el-table-column label="国家" min-width="130">
<template #default="{ row }">{{ row.countryName || row.countryCode || '-' }}</template>
</el-table-column>
<el-table-column label="分区" min-width="120">
<template #default="{ row }">{{ row.zoneName || row.zoneCode || '-' }}</template>
</el-table-column>
<el-table-column label="重量段" prop="weightLabel" min-width="130" />
<el-table-column label="运费单价" min-width="130" align="right">
<template #default="{ row }">
@@ -119,7 +122,7 @@ const details = computed(() => props.set.details || [])
const editable = computed(() => isChildEditable(props.set.status))
const subTitle = computed(() => isFixedType(props.set.setType)
? '按 产品 × 国家 × 重量段 覆盖固定价'
? '按 产品 × 国家 × 分区 × 重量段 覆盖固定价'
: '按产品限定折扣,留空走套餐默认折扣')
const hint = computed(() => isFixedType(props.set.setType)
@@ -8,10 +8,12 @@ import { listProduct, listProductCountries } from '@/api/price/product'
import { listSurchargeByProduct } from '@/api/price/surcharge'
import {
buildCountryOptions,
buildZoneOptions,
buildWeightOptions,
countryHasZones,
countryKeyOf,
formatWeightLabel,
matchCountry,
matchCountryZone,
toEditableDetailRow
} from '../utils/priceSet'
@@ -96,6 +98,14 @@ export function usePriceSetRefData() {
return buildCountryOptions(countryCache[row?.productCode] || [])
}
function zoneOptionsOf(row) {
return buildZoneOptions(countryCache[row?.productCode] || [], row || {})
}
function hasZonesOf(row) {
return countryHasZones(countryCache[row?.productCode] || [], row || {})
}
/** 明细行可选重量段 */
function weightOptionsOf(row) {
return buildWeightOptions(countryCache[row?.productCode] || [], row || {})
@@ -109,12 +119,14 @@ export function usePriceSetRefData() {
ensureSurcharges,
surchargesOf,
countryOptionsOf,
zoneOptionsOf,
hasZonesOf,
weightOptionsOf
}
}
/**
* 明细行级联:产品 → 国家 → 重量段,并带出默认运费 / 挂号费
* 明细行级联:产品 → 国家 → 分区 → 重量段,并带出默认运费 / 挂号费
*
* @param {Object} refData usePriceSetRefData() 的返回值(页面内共享同一份)
*/
@@ -124,6 +136,9 @@ export function useDetailCascade(refData) {
row.countryCode = undefined
row.countryName = undefined
row._countryKey = undefined
row.zoneId = undefined
row.zoneCode = undefined
row.zoneName = undefined
row.weightLabel = undefined
row.productCountryId = undefined
row.fvValue = undefined
@@ -144,6 +159,20 @@ export function useDetailCascade(refData) {
const opt = refData.countryOptionsOf(row).find(c => c.key === row._countryKey)
row.countryCode = opt ? opt.code : undefined
row.countryName = opt ? opt.name : undefined
row.zoneId = undefined
row.zoneCode = undefined
row.zoneName = undefined
row.weightLabel = undefined
row.productCountryId = undefined
row.fvValue = undefined
row.fvRegistrationFee = undefined
}
function onZoneChange(row) {
const opt = refData.zoneOptionsOf(row).find(z => z.key === row.zoneCode)
row.zoneId = opt ? opt.zoneId : undefined
row.zoneCode = opt ? opt.zoneCode : undefined
row.zoneName = opt ? opt.zoneName : undefined
row.weightLabel = undefined
row.productCountryId = undefined
row.fvValue = undefined
@@ -158,6 +187,9 @@ export function useDetailCascade(refData) {
return
}
row.productCountryId = opt.detailId
if (opt.zoneId != null) row.zoneId = opt.zoneId
if (opt.zoneCode) row.zoneCode = opt.zoneCode
if (opt.zoneName) row.zoneName = opt.zoneName
if (opt.freightPrice != null) row.fvValue = Number(opt.freightPrice)
if (opt.registrationFee != null) row.fvRegistrationFee = Number(opt.registrationFee)
}
@@ -171,7 +203,8 @@ export function useDetailCascade(refData) {
refData.ensureCountries(row.productCode).then(rows => {
if (!row.weightLabel) return
const hit = rows.find(item => {
if (!matchCountry(row, item)) return false
if (row.productCountryId != null && Number(item.detailId) === Number(row.productCountryId)) return true
if (!matchCountryZone(row, item)) return false
return formatWeightLabel(item.weightMin, item.weightMax) === row.weightLabel
|| (row.weightLabel.includes(String(item.weightMin)) && row.weightLabel.includes(String(item.weightMax)))
})
@@ -181,9 +214,12 @@ export function useDetailCascade(refData) {
row.productCountryId = hit.detailId
if (!row.countryCode) row.countryCode = hit.countryCode
if (!row.countryName) row.countryName = hit.countryName
if (hit.zoneId != null) row.zoneId = hit.zoneId
if (hit.zoneCode) row.zoneCode = hit.zoneCode
if (hit.zoneName) row.zoneName = hit.zoneName
})
return row
}
return { resetRow, onProductChange, onCountryChange, onWeightChange, hydrateRow }
return { resetRow, onProductChange, onCountryChange, onZoneChange, onWeightChange, hydrateRow }
}
@@ -115,6 +115,22 @@ export function matchCountry(detail, item) {
return !!detail.countryName && item.countryName === detail.countryName
}
/** detail row zone vs product-country row */
export function matchZone(detail, item) {
const detailHas = !!(detail.zoneCode || detail.zoneId)
const itemHas = !!(item.zoneCode || item.zoneId)
if (!detailHas && !itemHas) return true
if (detailHas !== itemHas) return false
if (detail.zoneId != null && item.zoneId != null) return Number(detail.zoneId) === Number(item.zoneId)
if (detail.zoneCode && item.zoneCode) return String(detail.zoneCode) === String(item.zoneCode)
return false
}
/** country + zone match */
export function matchCountryZone(detail, item) {
return matchCountry(detail, item) && matchZone(detail, item)
}
/**
* 国家下拉选项(按产品的国家配置去重)
* @param {Array} rows bms_product_country 行
@@ -141,9 +157,39 @@ export function buildCountryOptions(rows) {
* @param {Object} row 当前明细行(含国家)
* @returns {Array<{value, label, detailId, freightPrice, registrationFee}>}
*/
export function buildWeightOptions(rows, row) {
/**
* Zone options for selected country (from product-country rows).
* @returns {Array<{key, zoneId, zoneCode, zoneName, label}>}
*/
export function buildZoneOptions(rows, row) {
const map = new Map()
;(rows || []).filter(item => matchCountry(row, item)).forEach(item => {
if (!item.zoneCode && item.zoneId == null) return
const key = item.zoneCode || String(item.zoneId)
if (map.has(key)) return
map.set(key, {
key,
zoneId: item.zoneId,
zoneCode: item.zoneCode || undefined,
zoneName: item.zoneName || undefined,
label: item.zoneName || item.zoneCode || key
})
})
return [...map.values()]
}
/** Whether selected country has any zone rows */
export function countryHasZones(rows, row) {
return buildZoneOptions(rows, row).length > 0
}
export function buildWeightOptions(rows, row) {
const list = rows || []
if (countryHasZones(list, row) && !(row && (row.zoneCode || row.zoneId))) {
return []
}
const map = new Map()
list.filter(item => matchCountryZone(row, item)).forEach(item => {
if (item.weightMin == null && item.weightMax == null) return
const value = formatWeightLabel(item.weightMin, item.weightMax)
if (map.has(value)) return
@@ -151,6 +197,9 @@ export function buildWeightOptions(rows, row) {
value,
label: value,
detailId: item.detailId,
zoneId: item.zoneId,
zoneCode: item.zoneCode,
zoneName: item.zoneName,
freightPrice: item.freightPrice,
registrationFee: item.registrationFee
})
@@ -186,6 +235,9 @@ export function createEmptyDetailRow() {
countryCode: undefined,
countryName: undefined,
_countryKey: undefined,
zoneId: undefined,
zoneCode: undefined,
zoneName: undefined,
weightLabel: undefined,
productCountryId: undefined,
fvValue: undefined,
@@ -235,6 +287,9 @@ export function toDetailPayload(d) {
productName: d.productName,
countryCode: d.countryCode || undefined,
countryName: d.countryName || undefined,
zoneId: d.zoneId || undefined,
zoneCode: d.zoneCode || undefined,
zoneName: d.zoneName || undefined,
weightLabel: d.weightLabel || undefined,
productCountryId: d.productCountryId || undefined,
fvValue: d.fvValue,