This commit is contained in:
2026-10-20 23:34:11 +08:00
parent f155976f52
commit 13e4aa56ec
321 changed files with 6756 additions and 2110 deletions
@@ -1,4 +1,5 @@
<script setup lang="ts">
import '@/styles/adminWorkbench.css'
import InlineHelp from '@/shared/components/InlineHelp.vue'
import '@/styles/adminDensity.css'
import '@/modules/customers/customerDensity.css'
@@ -199,7 +200,7 @@ async function openEditor() {
async function confirmAbandon(): Promise<boolean> {
try {
await ElMessageBox.confirm('箱规尺寸尚未保存,确定放弃修改吗?', customerDialogTitle('放弃编辑', { tenantCode: props.customerCode, tenantName: props.customerName }), {
customClass: 'admin-density-confirm', confirmButtonText: '放弃', cancelButtonText: '继续编辑', type: 'warning',
customClass: 'admin-density-confirm admin-workbench', confirmButtonText: '放弃', cancelButtonText: '继续编辑', type: 'warning',
})
return true
} catch {
@@ -364,7 +365,7 @@ onBeforeUnmount(stopInitializationPolling)
</script>
<template>
<div class="dimension-rule-panel admin-density customer-density">
<div class="dimension-rule-panel admin-density customer-density admin-workbench">
<div v-if="initializationPending" class="rule-state initialization-state" role="status">
<Ruler :size="30" />
<strong>客户计费规则正在初始化</strong>
@@ -381,26 +382,25 @@ onBeforeUnmount(stopInitializationPolling)
<section v-else-if="initializationReady && config" class="rule-card">
<header class="rule-card-heading">
<div class="rule-title-row">
<h2>按箱规尺寸计算</h2>
<h2>按箱规尺寸计算<InlineHelp title="箱规计算说明"><p>长、宽、高各加 2X = 外径尺寸(X 为单边加宽,按实测尺寸匹配箱规)。</p><p>体积公式:外径长 × 外径宽 × 外径高 ÷ 1,000,000 = CBM。</p><p>低于最低档使用最低档,超出当前档自动匹配下一档,超出最大档使用最大档。</p></InlineHelp></h2>
<el-checkbox :model-value="defaultCalculationMode === 'DIMENSION'" :disabled="!canEdit || defaultModeSaving" @change="selectDefaultCalculationMode('DIMENSION', Boolean($event))">设为默认规则</el-checkbox>
</div>
<el-button v-if="canEdit" type="primary" :disabled="loading" @click="openEditor">编辑规则</el-button>
</header>
<div class="formula-band"><span>计算方式:<strong>长、宽、高各加 2X = 外径尺寸</strong>(X 为单边加宽,按实测尺寸匹配箱规)</span><span>体积公式:外径长 × 外径宽 × 外径高 ÷ 1,000,000 = CBM</span><span>低于最低档使用最低档,超出当前档自动匹配下一档,超出最大档使用最大档</span></div>
<DataTable :data="config.rules" :columns="columns" :loading="loading" row-key="boxType" empty-text="当前客户暂无箱规尺寸规则">
<div class="workbench-table"><DataTable :data="config.rules" :columns="columns" :loading="loading" row-key="boxType" empty-text="当前客户暂无箱规尺寸规则">
<template #boxTypeName="{ row }"><strong>{{ row.boxTypeName }}</strong></template>
<template #inner="{ row }">{{ formatDimension(row.innerLengthCm, row.innerWidthCm, row.innerHeightCm) }}</template>
<template #edgeAdditionCm="{ row }"><el-tag effect="light">单边 +{{ row.edgeAdditionCm }}cm</el-tag></template>
<template #outer="{ row }">{{ formatDimension(row.outerLengthCm, row.outerWidthCm, row.outerHeightCm) }}</template>
<template #volumeCbm="{ row }"><strong class="volume-value">{{ Number(row.volumeCbm).toFixed(3) }}</strong></template>
<template #empty><div class="rule-state"><Ruler :size="28" /><strong>当前客户暂无箱规尺寸规则</strong></div></template>
</DataTable>
</DataTable></div>
</section>
<div v-else-if="initializationReady" v-loading="loading" class="loading-state" />
<section v-if="initializationReady && canChargeView" class="rule-card charge-rule-card">
<header class="rule-card-heading"><div><div class="rule-title-row"><h2>箱规按权重系数计算<InlineHelp title="默认规则说明"><p>勾选后客户下单数据才会按该规则调整;未勾选时默认使用实测原尺寸(不调整)。</p></InlineHelp></h2><el-checkbox :model-value="defaultCalculationMode === 'WEIGHT'" :disabled="!canEdit || defaultModeSaving" @change="selectDefaultCalculationMode('WEIGHT', Boolean($event))">设为默认规则</el-checkbox></div></div></header>
<el-table v-loading="chargeLoading" :data="chargeDisplayRows" border stripe empty-text="暂无系数配置">
<div class="workbench-table"><el-table v-loading="chargeLoading" :data="chargeDisplayRows" border stripe empty-text="暂无系数配置">
<el-table-column prop="boxTypeName" label="箱规类型" min-width="110" />
<el-table-column label="内径尺寸(长×宽×高 cm)" min-width="240"><template #default="{ row }">{{ row.dimension ? formatDimension(row.dimension.innerLengthCm, row.dimension.innerWidthCm, row.dimension.innerHeightCm) : '—' }}</template></el-table-column>
<el-table-column label="权重系数" min-width="110"><template #default="{ row }"><strong>{{ row.coefficient }}</strong></template></el-table-column>
@@ -409,22 +409,22 @@ onBeforeUnmount(stopInitializationPolling)
<el-table-column label="重量(kg)" min-width="105"><template #default="{ row }">{{ row.weight ? row.weight.toFixed(2) : '—' }}</template></el-table-column>
<el-table-column label="体积重(kg)" min-width="115"><template #default="{ row }">{{ row.dimension ? (Number(row.dimension.volumeCbm) * row.coefficient * 167).toFixed(2) : '—' }}</template></el-table-column>
<el-table-column prop="remark" label="备注" min-width="180" align="left" header-align="left" class-name="charge-remark-column" label-class-name="charge-remark-column"><template #default="{ row }">{{ row.remark || '—' }}</template></el-table-column>
</el-table>
</el-table></div>
</section>
<AppDialog class="admin-density-dialog customer-density-dialog" v-model="editorVisible" :title="customerDialogTitle('编辑箱规尺寸 / 计算规则', { tenantCode: customerCode, tenantName: customerName })" fullscreenable width="min(1200px, calc(100vw - 28px))" :confirm-loading="editorSaving || chargeSaving" :show-cancel="false" :before-close="beforeEditorClose" @confirm="saveConfig">
<AppDialog class="admin-density-dialog customer-density-dialog admin-workbench is-dialog" v-model="editorVisible" :title="customerDialogTitle('编辑箱规尺寸 / 计算规则', { tenantCode: customerCode, tenantName: customerName })" fullscreenable width="min(1200px, calc(100vw - 28px))" :confirm-loading="editorSaving || chargeSaving" :show-cancel="false" :before-close="beforeEditorClose" @confirm="saveConfig">
<el-form ref="editorForm" :model="form" label-position="top" class="density-form density-dialog-sections">
<section class="density-dialog-section" data-tone="work">
<h3 class="density-dialog-section__title">按箱规尺寸计算<InlineHelp title="箱规计算公式"><p>计算公式:按长、宽、高匹配箱规;长、宽、高各加 2X(X 为单边加宽),体积(CBM) = 外径长 × 外径宽 × 外径高 ÷ 1,000,000</p></InlineHelp></h3>
<div class="editor-table-wrap">
<div class="editor-table-wrap workbench-evidence">
<table class="density-table density-table--editable editor-table" aria-label="箱规尺寸规则填写"><thead><tr><th>箱规类型</th><th>内径长(cm)</th><th>内径宽(cm)</th><th>内径高(cm)</th><th>单边加宽(cm)</th><th>备注</th></tr></thead><tbody>
<tr v-for="row in form.rules" :key="row.boxType"><th scope="row">{{ row.boxTypeName }}</th><td><el-input-number v-model="row.innerLengthCm" :min="0.001" :precision="3" :controls="false" placeholder="请输入内径长" /></td><td><el-input-number v-model="row.innerWidthCm" :min="0.001" :precision="3" :controls="false" placeholder="请输入内径宽" /></td><td><el-input-number v-model="row.innerHeightCm" :min="0.001" :precision="3" :controls="false" placeholder="请输入内径高" /></td><td><el-input-number v-model="row.edgeAdditionCm" :min="0.001" :max="100" :precision="3" :controls="false" placeholder="请输入边长加值" /></td><td><el-input v-model="row.remark" maxlength="500" placeholder="请输入备注(选填)" /></td></tr>
</tbody></table>
</div>
<div class="rule-save-row density-actions density-actions--footer"><el-button type="primary" :loading="editorSaving" @click="saveConfig">保存规则</el-button></div>
</section>
<section v-if="canChargeView" class="density-dialog-section" data-tone="work"><h3 class="density-dialog-section__title">箱规按权重系数计算<InlineHelp title="系数匹配规则"><p>长、宽、高任一项超出当前挡位时进入下一挡;命中规格的一个系数统一应用于长、宽、高。</p></InlineHelp></h3><div class="charge-rule-editor-wrap"><table class="density-table density-table--editable charge-rule-editor" aria-label="权重系数规则填写"><thead><tr><th>箱规类型</th><th>内径长(cm)</th><th>内径宽(cm)</th><th>内径高(cm)</th><th>权重系数</th><th>重量(kg)</th><th>备注</th></tr></thead><tbody><tr v-for="row in chargeRows" :key="`combined-${row.boxType}`"><th scope="row">{{ BOX_NAMES[row.boxType] }}</th><td><el-input-number v-model="row.length" :min="0.001" :precision="3" :controls="false" placeholder="请输入内径长" /></td><td><el-input-number v-model="row.width" :min="0.001" :precision="3" :controls="false" placeholder="请输入内径宽" /></td><td><el-input-number v-model="row.height" :min="0.001" :precision="3" :controls="false" placeholder="请输入内径高" /></td><td><el-input-number v-model="row.coefficient" :min="0.001" :precision="3" :controls="false" placeholder="请输入权重系数" /></td><td><el-input-number v-model="row.weight" :min="0.001" :precision="3" :controls="false" placeholder="请输入重量" /></td><td><el-input v-model="row.remark" maxlength="500" placeholder="请输入备注(选填)" /></td></tr></tbody></table></div><div class="rule-save-row density-actions density-actions--footer"><el-button type="primary" :loading="chargeSaving" @click="saveChargeConfig">保存系数规则</el-button></div></section>
<section v-if="canChargeView" class="density-dialog-section" data-tone="work"><h3 class="density-dialog-section__title">箱规按权重系数计算<InlineHelp title="系数匹配规则"><p>长、宽、高任一项超出当前挡位时进入下一挡;命中规格的一个系数统一应用于长、宽、高。</p></InlineHelp></h3><div class="charge-rule-editor-wrap workbench-evidence"><table class="density-table density-table--editable charge-rule-editor" aria-label="权重系数规则填写"><thead><tr><th>箱规类型</th><th>内径长(cm)</th><th>内径宽(cm)</th><th>内径高(cm)</th><th>权重系数</th><th>重量(kg)</th><th>备注</th></tr></thead><tbody><tr v-for="row in chargeRows" :key="`combined-${row.boxType}`"><th scope="row">{{ BOX_NAMES[row.boxType] }}</th><td><el-input-number v-model="row.length" :min="0.001" :precision="3" :controls="false" placeholder="请输入内径长" /></td><td><el-input-number v-model="row.width" :min="0.001" :precision="3" :controls="false" placeholder="请输入内径宽" /></td><td><el-input-number v-model="row.height" :min="0.001" :precision="3" :controls="false" placeholder="请输入内径高" /></td><td><el-input-number v-model="row.coefficient" :min="0.001" :precision="3" :controls="false" placeholder="请输入权重系数" /></td><td><el-input-number v-model="row.weight" :min="0.001" :precision="3" :controls="false" placeholder="请输入重量" /></td><td><el-input v-model="row.remark" maxlength="500" placeholder="请输入备注(选填)" /></td></tr></tbody></table></div><div class="rule-save-row density-actions density-actions--footer"><el-button type="primary" :loading="chargeSaving" @click="saveChargeConfig">保存系数规则</el-button></div></section>
</el-form>
<template #footer><div class="density-actions density-actions--footer"><el-button :disabled="editorSaving || chargeSaving" @click="closeEditor">取消</el-button></div></template>
</AppDialog>
@@ -434,25 +434,25 @@ onBeforeUnmount(stopInitializationPolling)
<style scoped>
.dimension-rule-panel { min-width: 0; }
.rule-heading { align-items: center; }
.rule-heading h2 { margin: 0; color: var(--cc-text); }.rule-heading p { margin: var(--admin-space-2, 6px) 0 0; color: var(--cc-muted); }
.rule-heading h2 { margin: 0; color: var(--workbench-text); }.rule-heading p { margin: var(--admin-space-2, 6px) 0 0; color: var(--workbench-muted); }
.heading-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--admin-space-3, 8px); }
.heading-actions :deep(.el-button) { margin-left: 0; }
.rule-card { min-width: 0; }.rule-card-heading { display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--cc-border); }.rule-card-heading h2 { margin: 0; color: var(--cc-text); }.formula-band { display: flex; align-items: center; flex-wrap: wrap; border-bottom: 1px solid var(--cc-border); background: var(--admin-label-bg, #f8fafc); color: var(--cc-muted); font-size: var(--admin-font-body, 13px); }.formula-band strong { color: #344054; }
.rule-title-row { display: flex; align-items: center; flex-wrap: wrap; gap: 14px; }.rule-title-row :deep(.el-checkbox) { height: 30px; margin-right: 0; padding: 0 var(--admin-space-4, 10px); border: 1px solid var(--cc-border); border-radius: 6px; background: var(--admin-surface, #fff); }.rule-title-row :deep(.el-checkbox.is-checked) { border-color: var(--cc-primary); background: var(--cc-primary-soft); }
.rule-state { display: flex; align-items: center; justify-content: center; flex-direction: column; gap: var(--admin-space-3, 8px); color: #8797b2; text-align: center; }.rule-state strong { color: #475467; font-size: var(--admin-font-body, 13px); }.rule-state p { margin: 0; color: var(--cc-muted); font-size: var(--admin-font-caption, 12px); }
.rule-card { min-width: 0; }.rule-card-heading { display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--workbench-border); }.rule-card-heading h2 { margin: 0; color: var(--workbench-text); }
.rule-title-row { display: flex; align-items: center; flex-wrap: wrap; gap: 14px; }.rule-title-row :deep(.el-checkbox) { height: 30px; margin-right: 0; padding: 0 var(--admin-space-4, 10px); border: 1px solid var(--workbench-border); border-radius: 6px; background: var(--workbench-surface); }.rule-title-row :deep(.el-checkbox.is-checked) { border-color: var(--workbench-primary); background: var(--workbench-accent-soft); }
.rule-state { display: flex; align-items: center; justify-content: center; flex-direction: column; gap: var(--admin-space-3, 8px); color: var(--workbench-muted); text-align: center; }.rule-state strong { color: var(--workbench-text); font-size: var(--admin-font-body, 13px); }.rule-state p { margin: 0; color: var(--workbench-muted); font-size: var(--admin-font-caption, 12px); }
.rule-error { display: flex; align-items: center; justify-content: space-between; gap: 18px; background: #fff4f3; }.rule-error strong { color: #7a271a; }.rule-error p { margin: 5px 0; color: #b42318; font-size: var(--admin-font-caption, 12px); }
.volume-value { color: #1570ef; } .editor-table,.charge-rule-editor { min-width:920px; table-layout:fixed; } .editor-table th:first-child,.charge-rule-editor th:first-child { width:100px; } .editor-table th:last-child,.charge-rule-editor th:last-child { width:20%; }.editor-table-wrap { overflow-x: auto; }.section-hint { margin: 5px 0 0; color: var(--cc-muted); font-size: var(--admin-font-caption, 12px); font-weight: 400; }
.volume-value { color: var(--workbench-primary); } .editor-table,.charge-rule-editor { min-width:920px; table-layout:fixed; } .editor-table th:first-child,.charge-rule-editor th:first-child { width:100px; } .editor-table th:last-child,.charge-rule-editor th:last-child { width:20%; }.editor-table-wrap { overflow-x: auto; }.section-hint { margin: 5px 0 0; color: var(--workbench-muted); font-size: var(--admin-font-caption, 12px); font-weight: 400; }
.charge-rule-editor-wrap { overflow-x: auto; }
.rule-save-row { padding: var(--admin-space-5, 12px) 0 var(--admin-space-1, 4px); }
.charge-rule-card :deep(.el-table) { color: #344054; font-size: var(--admin-font-body, 13px); }
.charge-rule-card :deep(.el-table th.el-table__cell) { background: var(--admin-label-bg, #f8fafc); color: #475467; font-size: var(--admin-font-body, 13px); font-weight: 650; }
.charge-rule-card :deep(.el-table td.el-table__cell) { color: #344054; font-size: var(--admin-font-body, 13px); }
.charge-rule-card :deep(.el-table .cell) { line-height: 1.5; text-align: center; }
.charge-rule-card :deep(.el-table .charge-remark-column .cell) { text-align: left; }
.charge-rule-card :deep(.el-table strong) { color: #1570ef; font-weight: 650; }
@media (max-width: 760px) { .rule-heading { align-items: stretch; flex-direction: column; }.rule-card-heading { align-items: stretch; flex-direction: column; }.rule-card-heading :deep(.el-button) { min-height: 44px; }.formula-band { align-items: flex-start; flex-direction: column; gap: 8px; }.rule-error { align-items: stretch; flex-direction: column; } }
.charge-rule-card :deep(.el-table strong) { color: var(--workbench-primary); font-weight: 650; }
@media (max-width: 760px) { .rule-heading { align-items: stretch; flex-direction: column; }.rule-card-heading { align-items: stretch; flex-direction: column; }.rule-card-heading :deep(.el-button) { min-height: 44px; }.rule-error { align-items: stretch; flex-direction: column; } }
</style>