From e2f75afb272a94bc656320163a73fe31d0325af0 Mon Sep 17 00:00:00 2001 From: wuweihua <88888888> Date: Mon, 14 Sep 2026 11:53:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=B7=E6=A0=BC=E5=A5=97?= =?UTF-8?q?=E9=A4=90=E6=A0=B7=E5=BC=8F=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../price/set/components/SurchargeTags.vue | 104 +++ .../set/components/create/StepBasicInfo.vue | 125 +++ .../set/components/create/StepCustomers.vue | 111 +++ .../set/components/create/StepDetails.vue | 186 +++++ .../components/detail/SetBasicInfoCard.vue | 102 +++ .../components/detail/SetCustomerDialog.vue | 165 ++++ .../components/detail/SetCustomerTable.vue | 113 +++ .../set/components/detail/SetDetailDialog.vue | 227 ++++++ .../set/components/detail/SetDetailHeader.vue | 123 +++ .../set/components/detail/SetDetailTable.vue | 189 +++++ .../set/components/detail/SetSectionCard.vue | 104 +++ .../set/composables/usePriceSetRefData.js | 189 +++++ bms-front/src/views/price/set/create.vue | 728 ++--------------- bms-front/src/views/price/set/detail.vue | 748 ++---------------- .../src/views/price/set/styles/dialog.scss | 214 +++++ .../src/views/price/set/utils/priceSet.js | 250 ++++++ 16 files changed, 2348 insertions(+), 1330 deletions(-) create mode 100644 bms-front/src/views/price/set/components/SurchargeTags.vue create mode 100644 bms-front/src/views/price/set/components/create/StepBasicInfo.vue create mode 100644 bms-front/src/views/price/set/components/create/StepCustomers.vue create mode 100644 bms-front/src/views/price/set/components/create/StepDetails.vue create mode 100644 bms-front/src/views/price/set/components/detail/SetBasicInfoCard.vue create mode 100644 bms-front/src/views/price/set/components/detail/SetCustomerDialog.vue create mode 100644 bms-front/src/views/price/set/components/detail/SetCustomerTable.vue create mode 100644 bms-front/src/views/price/set/components/detail/SetDetailDialog.vue create mode 100644 bms-front/src/views/price/set/components/detail/SetDetailHeader.vue create mode 100644 bms-front/src/views/price/set/components/detail/SetDetailTable.vue create mode 100644 bms-front/src/views/price/set/components/detail/SetSectionCard.vue create mode 100644 bms-front/src/views/price/set/composables/usePriceSetRefData.js create mode 100644 bms-front/src/views/price/set/styles/dialog.scss create mode 100644 bms-front/src/views/price/set/utils/priceSet.js diff --git a/bms-front/src/views/price/set/components/SurchargeTags.vue b/bms-front/src/views/price/set/components/SurchargeTags.vue new file mode 100644 index 00000000..f616b4f3 --- /dev/null +++ b/bms-front/src/views/price/set/components/SurchargeTags.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/bms-front/src/views/price/set/components/create/StepBasicInfo.vue b/bms-front/src/views/price/set/components/create/StepBasicInfo.vue new file mode 100644 index 00000000..abde7ea6 --- /dev/null +++ b/bms-front/src/views/price/set/components/create/StepBasicInfo.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/bms-front/src/views/price/set/components/create/StepCustomers.vue b/bms-front/src/views/price/set/components/create/StepCustomers.vue new file mode 100644 index 00000000..cd4a3fb2 --- /dev/null +++ b/bms-front/src/views/price/set/components/create/StepCustomers.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/bms-front/src/views/price/set/components/create/StepDetails.vue b/bms-front/src/views/price/set/components/create/StepDetails.vue new file mode 100644 index 00000000..9dad9eee --- /dev/null +++ b/bms-front/src/views/price/set/components/create/StepDetails.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/bms-front/src/views/price/set/components/detail/SetBasicInfoCard.vue b/bms-front/src/views/price/set/components/detail/SetBasicInfoCard.vue new file mode 100644 index 00000000..16b373c3 --- /dev/null +++ b/bms-front/src/views/price/set/components/detail/SetBasicInfoCard.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/bms-front/src/views/price/set/components/detail/SetCustomerDialog.vue b/bms-front/src/views/price/set/components/detail/SetCustomerDialog.vue new file mode 100644 index 00000000..d04fe643 --- /dev/null +++ b/bms-front/src/views/price/set/components/detail/SetCustomerDialog.vue @@ -0,0 +1,165 @@ + + + + + + diff --git a/bms-front/src/views/price/set/components/detail/SetCustomerTable.vue b/bms-front/src/views/price/set/components/detail/SetCustomerTable.vue new file mode 100644 index 00000000..b2581c56 --- /dev/null +++ b/bms-front/src/views/price/set/components/detail/SetCustomerTable.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/bms-front/src/views/price/set/components/detail/SetDetailDialog.vue b/bms-front/src/views/price/set/components/detail/SetDetailDialog.vue new file mode 100644 index 00000000..f95e5684 --- /dev/null +++ b/bms-front/src/views/price/set/components/detail/SetDetailDialog.vue @@ -0,0 +1,227 @@ + + + + + + diff --git a/bms-front/src/views/price/set/components/detail/SetDetailHeader.vue b/bms-front/src/views/price/set/components/detail/SetDetailHeader.vue new file mode 100644 index 00000000..f2a3394b --- /dev/null +++ b/bms-front/src/views/price/set/components/detail/SetDetailHeader.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/bms-front/src/views/price/set/components/detail/SetDetailTable.vue b/bms-front/src/views/price/set/components/detail/SetDetailTable.vue new file mode 100644 index 00000000..1f11511d --- /dev/null +++ b/bms-front/src/views/price/set/components/detail/SetDetailTable.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/bms-front/src/views/price/set/components/detail/SetSectionCard.vue b/bms-front/src/views/price/set/components/detail/SetSectionCard.vue new file mode 100644 index 00000000..c3e380d7 --- /dev/null +++ b/bms-front/src/views/price/set/components/detail/SetSectionCard.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/bms-front/src/views/price/set/composables/usePriceSetRefData.js b/bms-front/src/views/price/set/composables/usePriceSetRefData.js new file mode 100644 index 00000000..f7485fbf --- /dev/null +++ b/bms-front/src/views/price/set/composables/usePriceSetRefData.js @@ -0,0 +1,189 @@ +/** + * 价格套餐 · 参考数据与明细级联 + * + * 页面内共享一份(在父页面调用一次,作为 prop 传给各步骤组件), + * 避免产品 / 产品国家 / 附加费 被重复请求。 + */ +import { listProduct, listProductCountries } from '@/api/price/product' +import { listSurchargeByProduct } from '@/api/price/surcharge' +import { + buildCountryOptions, + buildWeightOptions, + countryKeyOf, + formatWeightLabel, + matchCountry, + toEditableDetailRow +} from '../utils/priceSet' + +/** + * 产品 / 产品国家 / 附加费 参考数据(带缓存) + */ +export function usePriceSetRefData() { + /** 产品下拉选项 */ + const productOptions = ref([]) + /** productCode -> bms_product_country 行 */ + const countryCache = reactive({}) + /** productCode -> 有效附加费关联 */ + const surchargeCache = reactive({}) + /** productCode -> 进行中的请求 */ + const surchargePending = {} + + const productMap = computed(() => new Map(productOptions.value.map(p => [p.value, p]))) + + /** 加载可选产品(在售) */ + function loadProducts() { + return listProduct({ status: '0', pageNum: 1, pageSize: 1000 }).then(res => { + productOptions.value = (res.rows || []).map(p => ({ + value: p.productCode, + label: `${p.productCode} ${p.productName || ''}`.trim(), + name: p.productName + })) + return productOptions.value + }) + } + + /** 产品名称 */ + function productNameOf(productCode) { + const hit = productMap.value.get(productCode) + return hit ? hit.name : productCode + } + + /** 确保产品的国家×重量段配置已加载(过滤已失效配置) */ + function ensureCountries(productCode) { + if (!productCode) return Promise.resolve([]) + if (countryCache[productCode]) return Promise.resolve(countryCache[productCode]) + return listProductCountries(productCode).then(res => { + const rows = (res.data || []).filter(item => !item.expireDate) + countryCache[productCode] = rows + return rows + }) + } + + /** 确保产品的有效附加费已加载(剔除 OF 燃油,结果缓存 + 请求去重) */ + function ensureSurcharges(productCode) { + if (!productCode) return Promise.resolve([]) + if (surchargeCache[productCode]) return Promise.resolve(surchargeCache[productCode]) + if (surchargePending[productCode]) return surchargePending[productCode] + surchargePending[productCode] = listSurchargeByProduct(productCode).then(res => { + const rows = (res.data || []).filter(item => String(item.ssCode || '').toLowerCase() !== 'of') + surchargeCache[productCode] = rows + return rows + }).catch(() => { + surchargeCache[productCode] = [] + return [] + }).finally(() => { + delete surchargePending[productCode] + }) + return surchargePending[productCode] + } + + /** 明细行命中的附加费(按产品 + 国家 + 重量段过滤) */ + function surchargesOf(row) { + if (!row || !row.productCode) return [] + const all = surchargeCache[row.productCode] || [] + return all.filter(s => { + if (s.countryCode && row.countryCode && String(s.countryCode).toUpperCase() !== String(row.countryCode).toUpperCase()) { + return false + } + if (s.detailId == null) return true + if (row.productCountryId != null && Number(s.detailId) === Number(row.productCountryId)) return true + return false + }) + } + + /** 明细行可选国家 */ + function countryOptionsOf(row) { + return buildCountryOptions(countryCache[row?.productCode] || []) + } + + /** 明细行可选重量段 */ + function weightOptionsOf(row) { + return buildWeightOptions(countryCache[row?.productCode] || [], row || {}) + } + + return { + productOptions, + loadProducts, + productNameOf, + ensureCountries, + ensureSurcharges, + surchargesOf, + countryOptionsOf, + weightOptionsOf + } +} + +/** + * 明细行级联:产品 → 国家 → 重量段,并带出默认运费 / 挂号费 + * + * @param {Object} refData usePriceSetRefData() 的返回值(页面内共享同一份) + */ +export function useDetailCascade(refData) { + /** 切换产品后清空下游字段 */ + function resetRow(row) { + row.countryCode = undefined + row.countryName = undefined + row._countryKey = undefined + row.weightLabel = undefined + row.productCountryId = undefined + row.fvValue = undefined + row.fvRegistrationFee = undefined + } + + /** 选择产品:回填名称、清空级联、预取附加费与国家配置 */ + function onProductChange(row, setType) { + row.productName = row.productCode ? refData.productNameOf(row.productCode) : undefined + resetRow(row) + if (!row.productCode) return + refData.ensureSurcharges(row.productCode) + if (setType === 'FIXED') refData.ensureCountries(row.productCode) + } + + /** 选择国家:回填国家码 / 名称,清空重量段与价格 */ + function onCountryChange(row) { + const opt = refData.countryOptionsOf(row).find(c => c.key === row._countryKey) + row.countryCode = opt ? opt.code : undefined + row.countryName = opt ? opt.name : undefined + row.weightLabel = undefined + row.productCountryId = undefined + row.fvValue = undefined + row.fvRegistrationFee = undefined + } + + /** 选择重量段:绑定产品国家明细,并带出默认运费 / 挂号费 */ + function onWeightChange(row) { + const opt = refData.weightOptionsOf(row).find(w => w.value === row.weightLabel) + if (!opt) { + row.productCountryId = undefined + return + } + row.productCountryId = opt.detailId + if (opt.freightPrice != null) row.fvValue = Number(opt.freightPrice) + if (opt.registrationFee != null) row.fvRegistrationFee = Number(opt.registrationFee) + } + + /** 回显历史明细行:补齐字段,并把旧重量段文案归一化为 min≤W≤max */ + function hydrateRow(d, setType) { + const row = toEditableDetailRow(d) + if (!row.productCode) return row + refData.ensureSurcharges(row.productCode) + if (setType !== 'FIXED') return row + refData.ensureCountries(row.productCode).then(rows => { + if (!row.weightLabel) return + const hit = rows.find(item => { + if (!matchCountry(row, item)) return false + return formatWeightLabel(item.weightMin, item.weightMax) === row.weightLabel + || (row.weightLabel.includes(String(item.weightMin)) && row.weightLabel.includes(String(item.weightMax))) + }) + if (!hit) return + row.weightLabel = formatWeightLabel(hit.weightMin, hit.weightMax) + row._countryKey = countryKeyOf(hit) + row.productCountryId = hit.detailId + if (!row.countryCode) row.countryCode = hit.countryCode + if (!row.countryName) row.countryName = hit.countryName + }) + return row + } + + return { resetRow, onProductChange, onCountryChange, onWeightChange, hydrateRow } +} diff --git a/bms-front/src/views/price/set/create.vue b/bms-front/src/views/price/set/create.vue index 62b59b6e..1a0f412f 100644 --- a/bms-front/src/views/price/set/create.vue +++ b/bms-front/src/views/price/set/create.vue @@ -7,255 +7,23 @@ - - - - - -
显示给客户的套餐对外名称
-
- -
-
-
FIXED 固定价
-
可不填明细(走产品默认价);有明细则按产品×国家×重量段覆盖运费+挂号费
-
-
-
DISCOUNT 折扣
-
按默认等级价统一乘折扣,可选产品范围
-
-
-
- - - % - 仅 DISCOUNT 模式生效 -
范围 1~99,88 表示默认价 × 0.88;FIXED 模式此值忽略
-
- - - - - -
空表示长期有效
-
- - - -
-
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - +
取消 @@ -268,346 +36,68 @@ - - diff --git a/bms-front/src/views/price/set/styles/dialog.scss b/bms-front/src/views/price/set/styles/dialog.scss new file mode 100644 index 00000000..5dcb2bdd --- /dev/null +++ b/bms-front/src/views/price/set/styles/dialog.scss @@ -0,0 +1,214 @@ +/** + * 套餐模块 · 表单弹窗统一样式 + * + * 为什么是全局样式而不是 scoped: + * el-dialog 使用 append-to-body 把内容传送到 body,且 .el-dialog 元素由 Element Plus + * 内部组件渲染,外层组件的 scoped 属性不会落到它身上,:deep() 也匹配不到。 + * 因此统一用 .ps-dialog 命名空间做全局覆盖,两个弹窗引用同一份,避免各写一套。 + * + * 用法:,内容区可用下面的 ps-* 工具类。 + */ + +/* ============ 1. 弹窗外壳 ============ */ +.ps-dialog { + border-radius: 10px; + overflow: hidden; + max-width: calc(100vw - 32px); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06); + + /* 头部:淡渐变 + 分隔线,标题收敛字号 */ + .el-dialog__header { + margin: 0; + padding: 15px 20px; + background: linear-gradient(180deg, #fbfcfe 0%, #ffffff 100%); + border-bottom: 1px solid #f0f2f5; + + .el-dialog__title { + font-size: 15px; + font-weight: 600; + line-height: 1.5; + color: #1f2d3d; + } + } + + .el-dialog__headerbtn { + top: 13px; + right: 14px; + width: 30px; + height: 30px; + border-radius: 6px; + transition: background-color 0.2s; + + .el-dialog__close { + font-size: 16px; + color: #a8abb2; + } + &:hover { + background: #f2f3f5; + + .el-dialog__close { + color: #303133; + } + } + } + + /* 主体:超高可滚动,滚动条收窄避免抢视觉 */ + .el-dialog__body { + padding: 20px; + max-height: 66vh; + overflow-y: auto; + color: #303133; + + &::-webkit-scrollbar { + width: 6px; + } + &::-webkit-scrollbar-thumb { + background: #dcdfe6; + border-radius: 3px; + + &:hover { + background: #c0c4cc; + } + } + &::-webkit-scrollbar-track { + background: transparent; + } + } + + /* 底部:浅底 + 分隔线,按钮右对齐且等宽 */ + .el-dialog__footer { + padding: 12px 20px; + text-align: right; + background: #fafbfc; + border-top: 1px solid #f0f2f5; + + .el-button { + min-width: 84px; + + & + .el-button { + margin-left: 8px; + } + } + } + + /* ============ 2. 表单排布 ============ */ + .el-form-item { + margin-bottom: 16px; + + &:last-child { + margin-bottom: 0; + } + } + + /* 两列布局下的行间距由 el-row 控制,去掉末项的额外留白 */ + .el-row .el-form-item { + margin-bottom: 16px; + } +} + +/* ============ 3. 内容区工具类 ============ */ +/* 数值字段:输入框 + 右侧单位。单位放 label 里会因 label 宽度固定而换行截断 */ +.ps-field { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + + &__input { + flex: 1; + min-width: 0; + } + + &__unit { + flex: none; + font-size: 12px; + line-height: 1.5; + color: #909399; + white-space: nowrap; + } +} + +/* 字段下方补充说明,独占一行 */ +.ps-hint { + flex: 1 0 100%; + width: 100%; + margin-top: 4px; + font-size: 12px; + line-height: 1.5; + color: #909399; + + /* 不挂在表单控件后面时,作为独立段落使用 */ + &--block { + flex: none; + margin-top: 0; + } +} + +/* 只读信息条:客户代码 chip + 名称 */ +.ps-readonly { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + min-height: 30px; + padding: 3px 10px; + font-size: 13px; + line-height: 1.5; + background: #f7f8fa; + border: 1px solid #eef0f3; + border-radius: 6px; + + &__code { + flex: none; + padding: 0 6px; + font-family: Consolas, Monaco, monospace; + font-size: 12px; + color: #606266; + background: #fff; + border: 1px solid #e9e9eb; + border-radius: 4px; + } + + &__name { + font-weight: 500; + color: #303133; + } + + &--empty { + color: #c0c4cc; + + .ps-readonly__name { + font-weight: 400; + color: #c0c4cc; + } + } +} + +/* 只读附属信息块(如附加费清单) */ +.ps-subbox { + width: 100%; + padding: 10px 12px; + background: #fafafa; + border: 1px solid #f0f0f0; + border-radius: 6px; + + &__head { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 8px; + font-size: 12px; + font-weight: 600; + color: #606266; + } + + &__count { + padding: 0 6px; + font-size: 11px; + font-weight: 500; + line-height: 16px; + color: #909399; + background: #f0f0f0; + border-radius: 8px; + } +} diff --git a/bms-front/src/views/price/set/utils/priceSet.js b/bms-front/src/views/price/set/utils/priceSet.js new file mode 100644 index 00000000..3d83884f --- /dev/null +++ b/bms-front/src/views/price/set/utils/priceSet.js @@ -0,0 +1,250 @@ +/** + * 价格套餐 · 纯函数与数据工厂 + * + * 只做数据转换,不依赖组件实例 / 字典 / 接口, + * create.vue、detail.vue 及 set/components 下的组件共用。 + */ +import { parseTime } from '@/utils/ruoyi' + +/** 计价类型枚举 */ +export const SET_TYPE = { + FIXED: 'FIXED', + DISCOUNT: 'DISCOUNT' +} + +/** 套餐状态枚举:D 草稿 / A 审核中 / E 生效中 / R 驳回 / S 已停用 */ +export const SET_STATUS = { + DRAFT: 'D', + AUDIT: 'A', + EFFECTIVE: 'E', + REJECT: 'R', + STOPPED: 'S' +} + +const STATUS_LABEL = { + D: '草稿', + A: '审核中', + E: '生效中', + R: '驳回', + S: '已停用' +} + +const STATUS_TAG = { + D: 'warning', + A: 'primary', + E: 'success', + R: 'danger', + S: 'info' +} + +/** 默认取值方式 */ +export const DEFAULT_ROUND_MODE = 'HALF_UP' + +/** 状态文案(已过期优先) */ +export function setStatusLabel(set) { + if (set && set.expired) return '已过期' + return STATUS_LABEL[set && set.status] || (set && set.status) || '-' +} + +/** 状态标签色(已过期优先) */ +export function setStatusTagType(set) { + if (set && set.expired) return 'danger' + return STATUS_TAG[set && set.status] || 'info' +} + +/** 明细 / 客户是否可维护:草稿、驳回、生效中 */ +export function isChildEditable(status) { + return [SET_STATUS.DRAFT, SET_STATUS.REJECT, SET_STATUS.EFFECTIVE].includes(status) +} + +/** 日期显示 yyyy-MM-dd */ +export function formatDate(val) { + return val ? parseTime(val, '{y}-{m}-{d}') : '' +} + +/** 有效期区间文案,结束为空表示长期 */ +export function formatDateRange(begin, end) { + return `${formatDate(begin) || '-'} ~ ${end ? formatDate(end) : '长期'}` +} + +/** 是否固定价模式 */ +export function isFixedType(setType) { + return setType === SET_TYPE.FIXED +} + +/** 是否折扣模式 */ +export function isDiscountType(setType) { + return setType === SET_TYPE.DISCOUNT +} + +/** 计价类型展示文案 */ +export function setTypeLabel(setType) { + return isFixedType(setType) ? 'FIXED 固定价' : 'DISCOUNT 折扣' +} + +/** + * 数字转字符串,抹掉浮点误差尾巴 + * @param {number|string} v + * @returns {string} 0.1 + 0.2 -> '0.3' + */ +export function trimNum(v) { + if (v == null || v === '') return '' + const n = Number(v) + if (Number.isNaN(n)) return String(v) + return String(parseFloat(n.toFixed(6))) +} + +/** 重量段展示文案:1≤W≤2 */ +export function formatWeightLabel(min, max) { + return `${trimNum(min)}≤W≤${trimNum(max)}` +} + +/** 国家唯一键:优先国家三字码,其次国家名称 */ +export function countryKeyOf(row) { + if (!row) return '' + if (row.countryCode) return row.countryCode + if (row.countryName) return 'N:' + row.countryName + return '' +} + +/** 明细行的国家 与 产品国家配置行 是否指向同一国家 */ +export function matchCountry(detail, item) { + if (detail.countryCode && item.countryCode) { + return detail.countryCode === item.countryCode + } + return !!detail.countryName && item.countryName === detail.countryName +} + +/** + * 国家下拉选项(按产品的国家配置去重) + * @param {Array} rows bms_product_country 行 + * @returns {Array<{key, code, name, label}>} + */ +export function buildCountryOptions(rows) { + const map = new Map() + ;(rows || []).forEach(item => { + const key = countryKeyOf(item) + if (!key || map.has(key)) return + map.set(key, { + key, + code: item.countryCode || undefined, + name: item.countryName, + label: item.countryCode ? `${item.countryName} ${item.countryCode}` : item.countryName + }) + }) + return [...map.values()] +} + +/** + * 重量段下拉选项(按已选国家过滤去重) + * @param {Array} rows bms_product_country 行 + * @param {Object} row 当前明细行(含国家) + * @returns {Array<{value, label, detailId, freightPrice, registrationFee}>} + */ +export function buildWeightOptions(rows, row) { + const map = new Map() + ;(rows || []).filter(item => matchCountry(row, item)).forEach(item => { + if (item.weightMin == null && item.weightMax == null) return + const value = formatWeightLabel(item.weightMin, item.weightMax) + if (map.has(value)) return + map.set(value, { + value, + label: value, + detailId: item.detailId, + freightPrice: item.freightPrice, + registrationFee: item.registrationFee + }) + }) + return [...map.values()] +} + +/** 附加费金额展示:RATE 转百分比,其余带币种 */ +export function formatChargeValue(row) { + if (!row || row.chargeValue == null) return '-' + const v = Number(row.chargeValue) + if (row.chargeType === 'RATE') return (v * 100).toFixed(2) + '%' + return v + ' ' + (row.currency || 'CNY') +} + +/** 附加费作用范围文案 */ +export function surchargeScopeLabel(s) { + if (s.detailId == null) return '产品级' + const w = s.weightMin != null && s.weightMax != null ? `${s.weightMin}-${s.weightMax}kg` : '-' + return `${s.countryName || '-'} · ${w}` +} + +/** 附加费标签文案 */ +export function surchargeTagLabel(s) { + return `${s.scName} ${formatChargeValue(s)}` +} + +/** 新增一条空明细行 */ +export function createEmptyDetailRow() { + return { + productCode: undefined, + productName: undefined, + countryCode: undefined, + countryName: undefined, + _countryKey: undefined, + weightLabel: undefined, + productCountryId: undefined, + fvValue: undefined, + fvRegistrationFee: undefined, + discountValue: undefined, + roundMode: DEFAULT_ROUND_MODE + } +} + +/** 明细行 -> 可编辑行(补齐取值方式与国家键) */ +export function toEditableDetailRow(row) { + return { + ...row, + roundMode: row.roundMode || DEFAULT_ROUND_MODE, + _countryKey: countryKeyOf(row) + } +} + +/** 新增一条空客户绑定行 */ +export function createEmptyCustomerRow({ effectiveDate, expireDate, seq = 1 } = {}) { + return { + customerId: undefined, + customerCode: undefined, + customerName: '', + bindPriority: seq, + prBegindate: effectiveDate, + prEnddate: expireDate + } +} + +/** 新建套餐的基本信息默认值 */ +export function createEmptyBasicInfo() { + return { + priceSetName: '', + setType: SET_TYPE.FIXED, + defaultDiscount: 88, + effectiveDate: undefined, + expireDate: undefined, + priceSetNote: '' + } +} + +/** 明细行 -> 提交后端的 payload */ +export function toDetailPayload(d) { + return { + productCode: d.productCode, + productName: d.productName, + countryCode: d.countryCode || undefined, + countryName: d.countryName || undefined, + weightLabel: d.weightLabel || undefined, + productCountryId: d.productCountryId || undefined, + fvValue: d.fvValue, + fvRegistrationFee: d.fvRegistrationFee, + discountValue: d.discountValue, + roundMode: d.roundMode || DEFAULT_ROUND_MODE + } +} + +/** 客户绑定行 -> 提交后端的 payload */ +export function toCustomerPayload(c) { + return { ...c } +}