@@ -717,6 +853,8 @@ defineExpose({ saveDraft, submitForReview, updateActualWeights, mayMaintain, may
.measurement-declaration h4 { margin: 0 0 10px; color: #344054; font-size: 12px; font-weight: 700; }
.measurement-section__heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; margin-bottom: 16px; }
.measurement-section__heading > div { min-width: 0; }
+.measurement-section-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }.measurement-section-actions :deep(.el-button) { margin-left: 0; }
+.warehouse-suggestion-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }.warehouse-suggestion-form :deep(.el-select),.warehouse-suggestion-form :deep(.el-input-number) { width: 100%; }.warehouse-suggestion-form :deep(.el-form-item:last-child) { grid-column: 1 / -1; }.suggestion-preview { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 4px 0 0; border: 1px solid var(--cc-border); border-radius: 6px; }.suggestion-preview div { padding: 12px; border-right: 1px solid var(--cc-border); }.suggestion-preview div:last-child { border-right: 0; }.suggestion-preview dt { color: var(--cc-muted); font-size: 11px; }.suggestion-preview dd { margin: 5px 0 0; color: var(--cc-text); font-size: 13px; font-weight: 650; }
.measurement-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 24px; }
.measurement-data-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; }
.measurement-form label { display: flex; min-width: 0; flex-direction: column; gap: 7px; color: #344054; font-size: 12px; font-weight: 650; }
@@ -738,6 +876,7 @@ defineExpose({ saveDraft, submitForReview, updateActualWeights, mayMaintain, may
.measurement-table :deep(.el-table__cell) { padding: 7px 0; }
.measurement-table :deep(.cell) { line-height: 1.35; }
.measurement-table :deep(.measurement-table__row--error > td.el-table__cell) { background: var(--el-color-danger-light-9); }
+.warehouse-suggestion-table { display: grid; gap: 10px; margin-top: 18px; }.warehouse-suggestion-table h3 { margin: 0; color: var(--cc-text); font-size: 14px; }
.measurement-box-code { font-weight: 650; }
.measurement-count-editor { width: 104px !important; }
.measurement-row-details { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px 20px; margin: 0; padding: 4px 18px 8px 62px; }
diff --git a/src/modules/inbounds/measurement/types.ts b/src/modules/inbounds/measurement/types.ts
index 4484c21d..389d638a 100644
--- a/src/modules/inbounds/measurement/types.ts
+++ b/src/modules/inbounds/measurement/types.ts
@@ -13,6 +13,7 @@ export type InboundMeasurementProcessStatus =
export interface InboundMeasurementItem {
id: MeasurementIdentifier
+ inboundDetailId?: MeasurementIdentifier
boxCode: string | null
itemNo: string
boxSpecification: string
@@ -33,6 +34,8 @@ export interface InboundMeasurementItem {
outerLengthCm: number | null
outerWidthCm: number | null
outerHeightCm: number | null
+ chargeRuleId?: MeasurementIdentifier | null
+ coefficientMode?: 'ADD' | 'MULTIPLY' | null
remark: string | null
}
@@ -93,21 +96,24 @@ export interface InboundMeasurementDetail {
}
export interface SaveInboundMeasurementItemRequest {
- boxCode: string | null
+ inboundDetailId?: MeasurementIdentifier
+ boxCode?: string | null
itemNo: string
boxSpecification: string
packageCount: number
lengthCm: number
widthCm: number
heightCm: number
- unitWeightKg: number
+ unitWeightKg?: number
intelligentImageFileId: MeasurementIdentifier | null
- machineNo: string | null
- measuredAt: string | null
+ machineNo?: string | null
+ measuredAt?: string | null
overLimitWarning: string | null
outerLengthCm: number | null
outerWidthCm: number | null
outerHeightCm: number | null
+ chargeRuleId?: MeasurementIdentifier | null
+ coefficientMode?: 'ADD' | 'MULTIPLY' | null
remark: string | null
}
diff --git a/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts b/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts
index 32f62461..c1c6bfb7 100644
--- a/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts
+++ b/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts
@@ -297,6 +297,14 @@ describe('UnifiedOrdersView contract', () => {
expect(source).toContain('
')
expect(source).toContain("{ key: 'girthCm', label: '箱规周长(cm)' }")
+ expect(source).toContain('label="#" prop="itemNo"')
+ expect(source).toContain("const actualMeasuredColumns = measuredColumns.filter((column) => column.key !== 'itemNo')")
+ expect(source).toContain('v-for="column in actualMeasuredColumns"')
+ expect(source).toContain('仓库修改外箱(长)')
+ expect(source).toContain('仓库修改外箱(宽)')
+ expect(source).toContain('仓库修改外箱(高)')
+ expect(source).toContain('saveWarehouseOuterDimensions')
+ expect(source).toContain('相关数值已重新计算')
expect(source).toContain('runMoreAction($event, row as UnifiedOrderResource)')
expect(source).toContain(':aria-label="moreUnifiedOrderActionsLabel(row as UnifiedOrderResource)"')
expect(source).toContain(':title="moreUnifiedOrderActionsLabel(row as UnifiedOrderResource)"')
@@ -313,8 +321,37 @@ describe('UnifiedOrdersView contract', () => {
expect(source).not.toContain('formatNumber(2 * (width + height))')
})
+ it('shows the matched dimension addition and enabled multiplier in calculation choices', () => {
+ expect(source).toContain('getCustomerDimensionConfig')
+ expect(source).toContain('matchingDimensionRule')
+ expect(source).toContain('`原尺寸 + ${values[0]}cm`')
+ expect(source).toContain('`原尺寸 × ${rule.lengthFactor}`')
+ expect(source).toContain('label="箱规尺寸" value="ADD"')
+ expect(source).toContain('label="箱规系数" value="MULTIPLY"')
+ })
+
+ it('keeps the three section calculation modes independent and recalculates their display rows', () => {
+ expect(source).toContain("measurementSectionModes = reactive
>")
+ expect(source).toContain(':model-value="measurementSectionModes.customer"')
+ expect(source).toContain(':model-value="measurementSectionModes.outer"')
+ expect(source).toContain(':model-value="measurementSectionModes.actual"')
+ expect(source).toContain("updateMeasurementSectionMode('customer', $event)")
+ expect(source).toContain("updateMeasurementSectionMode('outer', $event)")
+ expect(source).toContain("updateMeasurementSectionMode('actual', $event)")
+ expect(source).toContain('calculatedMeasurementRow(row, measurementSectionModes.customer, true)')
+ expect(source).toContain('}, measurementSectionModes.outer)')
+ expect(source).toContain('calculatedMeasurementRow(row as unknown as Record, measurementSectionModes.actual)')
+ expect(source).toContain('rawDimensions.map((value) => value + rule.edgeAdditionCm)')
+ expect(source).toContain('matchedSpecificationCoefficient(chargeRule, calculationSource)')
+ expect(source).toContain('listCustomerChargeRules(customerId, tenantId)')
+ expect(source).toContain('matchedDimensions.map((value) => roundedUp(value * specificationCoefficient, 3))')
+ expect(source).not.toContain('[chargeRule.lengthFactor, chargeRule.widthFactor, chargeRule.heightFactor]')
+ expect(source).not.toContain('column.key === \'coefficientMode\'')
+ expect(source).not.toContain('@change="updateMeasurementCalculationMode(null, $event)"')
+ })
+
it('binds dimension and weight evidence to their formal server records without mixing galleries', () => {
- expect(source).toContain(' {
it('matches the prototype table grid and distinguishes container identifiers', () => {
expect(source).toContain('row-key="orderId" border stripe')
- expect(source).toContain(']*border stripe/g)).toHaveLength(2)
- expect(source).toContain('
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch, type Component } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
-import { ArrowLeft, Box, ChevronDown, ChevronUp, CircleCheck, Download, Eye, GitBranch, History, Link2, ListChecks, MoreHorizontal, PackageOpen, Ruler, SlidersHorizontal, StickyNote, WalletCards } from '@lucide/vue'
+import { ArrowLeft, Box, ChevronDown, ChevronUp, CircleCheck, Download, Eye, GitBranch, History, Link2, ListChecks, MoreHorizontal, PackageOpen, Ruler, Send, SlidersHorizontal, StickyNote, WalletCards } from '@lucide/vue'
import { useRoute, useRouter } from 'vue-router'
import {
getPlatformUnifiedOrder,
@@ -57,12 +57,20 @@ import {
} from '@/modules/unified-orders/status'
import {
downloadWeightImage,
+ calculateInboundFee,
+ confirmInboundFee,
+ createFeeActionIdempotencyKey,
getInboundMeasurement,
+ saveInboundMeasurementDraft,
+ getLatestInboundFee,
InboundFeePanel,
InboundMeasurementDialog,
MeasurementEvidenceThumbnail,
+ sendInboundFee,
type InboundMeasurementDetail,
} from '@/modules/inbounds'
+import { listCustomerChargeRules, type CustomerChargeRule } from '@/modules/fba-pricing'
+import { getCustomerDimensionConfig, type CustomerDimensionConfig, type CustomerDimensionRule } from '@/modules/dimension-config'
import {
OrderBasicInfoIcon,
OrderCargoInfoIcon,
@@ -151,14 +159,44 @@ const measurementDetail = ref(null)
const measurementLoading = ref(false)
const measurementError = ref(null)
const measurementLoadedKey = ref('')
+const measurementChargeRules = ref([])
+const measurementChargeRulesLoading = ref(false)
+const measurementChargeRulesLoadedKey = ref('')
+const measurementDimensionConfig = ref(null)
+const measurementDimensionConfigLoading = ref(false)
+const measurementDimensionConfigLoadedKey = ref('')
+const measurementStrategySavingId = ref('')
+const measurementOuterSavingId = ref('')
const measurementPreviewOpen = ref(false)
const measurementPreviewLoading = ref(false)
const measurementPreviewError = ref(null)
const measurementPreviewUrl = ref('')
const measurementPreviewTitle = ref('测量图片')
const measurementCollapsed = reactive({ customer: false, outer: false, actual: false })
+type MeasurementSection = keyof typeof measurementCollapsed
+type MeasurementCalculationMode = 'ACTUAL' | 'ADD' | 'MULTIPLY'
+const measurementSectionModes = reactive>({
+ customer: 'ACTUAL', outer: 'ACTUAL', actual: 'ACTUAL',
+})
const cargoImagePreviewUrls = new Set()
const measurementDialogOpen = ref(false)
+const openWarehouseSuggestionOnMeasurementLoad = ref(false)
+const confirmFeeSending = ref(false)
+const feeConfirmOpen = ref(false)
+const feeConfirmWeight = ref(null)
+const feeConfirmVolume = ref(null)
+const feeConfirmRows = computed(() => {
+ const summary = detail.value?.summary
+ const measurement = summary?.measurement
+ const actualWeight = Number(measurement?.actualGrossWeightKg)
+ const actualVolume = Number(measurement?.actualVolumeCbm)
+ const customerTotals = measurementTotals(customerMeasurementRows.value)
+ return [
+ { source: '客户下单数据', count: summary?.totalPackageCount, weight: customerTotals.gross, volume: customerTotals.volume ?? summary?.expectedVolumeCbm },
+ { source: '外箱规数据', count: measurementTotals(outerMeasurementRows.value).count, weight: measurementTotals(outerMeasurementRows.value).gross, volume: measurementTotals(outerMeasurementRows.value).volume },
+ { source: '实际测量数据', count: measurementDetail.value?.actualPackageCount, weight: actualWeight, volume: actualVolume },
+ ]
+})
const smartLoadingOpen = ref(false)
const measurementOrder = ref(null)
const loadingAssociation = ref(null)
@@ -213,8 +251,140 @@ const customerMeasurementColumns = [
]
const measuredColumns = [
...customerMeasurementColumns.slice(0, 7), { key: 'girthCm', label: '箱规周长(cm)' }, { key: 'totalWeightKg', label: '总毛重(T.K.GW)' },
- { key: 'totalVolumeCbm', label: '总体积(T.CBM)' }, { key: 'volumeWeightKg', label: '总材积' }, { key: 'remark', label: '备注' },
+ { key: 'totalVolumeCbm', label: '总体积(T.CBM)' }, { key: 'volumeWeightKg', label: '总材积' },
+ { key: 'remark', label: '备注' },
]
+const outerColumns = measuredColumns
+const actualMeasuredColumns = measuredColumns.filter((column) => column.key !== 'itemNo')
+
+function sortedDimensions(row: Record) {
+ const values = [Number(row.lengthCm), Number(row.widthCm), Number(row.heightCm)]
+ return values.every((value) => Number.isFinite(value) && value > 0)
+ ? values.sort((left, right) => right - left)
+ : null
+}
+
+function matchingDimensionRule(row: Record): CustomerDimensionRule | null {
+ const measured = sortedDimensions(row)
+ if (!measured) return null
+ return [...(measurementDimensionConfig.value?.rules || [])]
+ .sort((left, right) => (
+ left.innerLengthCm * left.innerWidthCm * left.innerHeightCm
+ - right.innerLengthCm * right.innerWidthCm * right.innerHeightCm
+ ))
+ .find((rule) => {
+ const inner = [rule.innerLengthCm, rule.innerWidthCm, rule.innerHeightCm].sort((left, right) => right - left)
+ return measured.every((value, index) => value <= inner[index])
+ }) || null
+}
+
+function addCoefficientLabel(row?: Record) {
+ const rows = row ? [row] : (measurementDetail.value?.items || []) as unknown as Record[]
+ const values = [...new Set(rows.map((item) => matchingDimensionRule(item)?.edgeAdditionCm).filter((value) => value != null))]
+ if (values.length === 1) return `原尺寸 + ${values[0]}cm`
+ if (measurementDimensionConfigLoading.value) return '原尺寸 + 加载中…'
+ return values.length > 1 ? '原尺寸 + 按箱规匹配' : '原尺寸 + 未匹配箱规'
+}
+
+function multiplyCoefficientLabel() {
+ const rule = measurementChargeRules.value.find((item) => item.defaultRule && item.status === 'ACTIVE')
+ if (rule) return `原尺寸 × ${rule.lengthFactor}`
+ return measurementChargeRulesLoading.value ? '原尺寸 × 加载中…' : '原尺寸 × 未启用系数'
+}
+
+function matchedSpecificationCoefficient(rule: CustomerChargeRule, row: Record) {
+ const tiers = [...(rule.remark || '').matchAll(
+ /(SMALL|MEDIUM|LARGE|EXTRA_LARGE):(\d+(?:\.\d+)?),(\d+(?:\.\d+)?),(\d+(?:\.\d+)?);系数:(\d+(?:\.\d+)?)/g,
+ )].map((match) => ({
+ dimensions: [Number(match[2]), Number(match[3]), Number(match[4])].sort((left, right) => right - left),
+ coefficient: Number(match[5]),
+ })).filter((tier) => tier.dimensions.every((value) => Number.isFinite(value) && value > 0)
+ && Number.isFinite(tier.coefficient) && tier.coefficient > 0)
+ .sort((left, right) => left.dimensions.reduce((total, value) => total * value, 1)
+ - right.dimensions.reduce((total, value) => total * value, 1))
+ if (!tiers.length) return Number(rule.lengthFactor || 1)
+ const measured = [Number(row.lengthCm), Number(row.widthCm), Number(row.heightCm)]
+ .sort((left, right) => right - left)
+ const matched = tiers.find((tier) => measured.every((value, index) => value <= tier.dimensions[index]))
+ return (matched || tiers[tiers.length - 1]).coefficient
+}
+
+const activeMeasurementChargeRule = computed(() => measurementChargeRules.value
+ .find((rule) => rule.defaultRule && rule.status === 'ACTIVE') || null)
+const multiplyModeDisabled = computed(() => !measurementChargeRulesLoading.value && !activeMeasurementChargeRule.value)
+
+function roundedUp(value: number, digits: number) {
+ const factor = 10 ** digits
+ return Math.ceil((value - Number.EPSILON) * factor) / factor
+}
+
+function calculatedMeasurementRow(
+ source: Record,
+ mode: MeasurementCalculationMode,
+ customerSource = false,
+) {
+ const calculationSource = !customerSource && mode !== 'ACTUAL'
+ ? {
+ ...source,
+ lengthCm: source.outerLengthCm ?? source.lengthCm,
+ widthCm: source.outerWidthCm ?? source.widthCm,
+ heightCm: source.outerHeightCm ?? source.heightCm,
+ }
+ : source
+ const rule = matchingDimensionRule(calculationSource)
+ const rawDimensions = [Number(calculationSource.lengthCm), Number(calculationSource.widthCm), Number(calculationSource.heightCm)]
+ if (!rawDimensions.every((value) => Number.isFinite(value) && value > 0)) return { ...source }
+
+ const matchedDimensions = mode === 'ADD' && rule
+ ? rawDimensions.map((value) => value + rule.edgeAdditionCm)
+ : rawDimensions
+ const chargeRule = activeMeasurementChargeRule.value
+ const specificationCoefficient = mode === 'MULTIPLY' && chargeRule
+ ? matchedSpecificationCoefficient(chargeRule, calculationSource)
+ : 1
+ const [lengthCm, widthCm, heightCm] = matchedDimensions.map((value) => roundedUp(value * specificationCoefficient, 3))
+ const packageCount = Number(source.packageCount)
+ const unitVolumeCbm = mode === 'ADD' && rule
+ ? Math.round((lengthCm * widthCm * heightCm / 1_000_000) * 1000) / 1000
+ : roundedUp(lengthCm * widthCm * heightCm / 1_000_000, 4)
+ const totalVolumeCbm = Number.isFinite(packageCount) ? roundedUp(unitVolumeCbm * packageCount, 4) : null
+ const sourceUnitWeight = customerSource
+ ? Number(source.grossWeightKg) / packageCount
+ : Number(source.unitWeightKg)
+ const unitWeightKg = Number.isFinite(sourceUnitWeight) ? sourceUnitWeight : null
+ const totalWeightKg = unitWeightKg != null && Number.isFinite(packageCount) ? unitWeightKg * packageCount : null
+ return {
+ ...source,
+ lengthCm, widthCm, heightCm, unitWeightKg, unitVolumeCbm, totalWeightKg, totalVolumeCbm,
+ girthCm: roundedUp(lengthCm + 2 * (widthCm + heightCm), 3),
+ volumeWeightKg: totalVolumeCbm == null ? null : roundedUp(totalVolumeCbm * 167, 3),
+ remark: customerSource ? source.cargoRemark : source.remark,
+ }
+}
+
+const customerMeasurementRows = computed(() => (detail.value?.order.cargoes || []).map((row) => (
+ calculatedMeasurementRow(row, measurementSectionModes.customer, true)
+)))
+const outerMeasurementRows = computed(() => (measurementDetail.value?.items || []).map((row) => {
+ const source = row as unknown as Record
+ return calculatedMeasurementRow({
+ ...source,
+ lengthCm: source.outerLengthCm ?? source.lengthCm,
+ widthCm: source.outerWidthCm ?? source.widthCm,
+ heightCm: source.outerHeightCm ?? source.heightCm,
+ }, measurementSectionModes.outer)
+}))
+const actualMeasurementRows = computed(() => (measurementDetail.value?.items || []).map((row) => (
+ calculatedMeasurementRow(row as unknown as Record, measurementSectionModes.actual)
+)))
+
+function updateMeasurementSectionMode(section: MeasurementSection, mode: MeasurementCalculationMode) {
+ if (mode === 'MULTIPLY' && !activeMeasurementChargeRule.value) {
+ ElMessage.warning('该客户尚未启用系数配置,请先到客户计费规则中设置默认规则')
+ return
+ }
+ measurementSectionModes[section] = mode
+}
const statusTabs = computed(() => [
{ value: '' as const, label: '全部' },
@@ -529,7 +699,12 @@ function feeReviewLabel(value: string | null | undefined) {
return unifiedOrderReviewStatusLabel(value)
}
-function measurementValue(row: Record, key: string, customerSource = false) {
+function measurementValue(row: Record, key: string, customerSource = false, outerSource = false) {
+ if (key === 'coefficientMode') return measurementCalculationLabel(row)
+ if (outerSource && ['lengthCm', 'widthCm', 'heightCm'].includes(key)) {
+ const outerValue = row[`outer${key[0].toUpperCase()}${key.slice(1)}`]
+ return outerValue == null ? '—' : formatNumber(Number(outerValue), 6)
+ }
if (customerSource && key === 'unitWeightKg') {
const total = Number(row.grossWeightKg)
const count = Number(row.packageCount)
@@ -546,6 +721,191 @@ function measurementValue(row: Record, key: string, customerSou
return typeof value === 'number' ? formatNumber(value, 6) : String(value)
}
+function measurementCalculationLabel(row: Record) {
+ if (measurementCalculationModeForRow(row) === 'MULTIPLY') {
+ return `乘以${measurementChargeRuleName(row.chargeRuleId)}`
+ }
+ if (measurementCalculationModeForRow(row) === 'ACTUAL') return '实测原尺寸(不调整)'
+ return addCoefficientLabel(row).replace('原尺寸 + ', '加')
+}
+
+function measurementCalculationModeForRow(row: Record): MeasurementCalculationMode {
+ if (row.coefficientMode === 'MULTIPLY') return 'MULTIPLY'
+ if (row.coefficientMode === 'ADD') return 'ADD'
+ const itemNo = normalizedIdentifier(row.itemNo)
+ const measuredItem = measurementDetail.value?.items.find((item) => normalizedIdentifier(item.itemNo) === itemNo)
+ return measuredItem?.coefficientMode === 'MULTIPLY' ? 'MULTIPLY' : measuredItem?.coefficientMode === 'ADD' ? 'ADD' : 'ACTUAL'
+}
+
+async function updateMeasurementCalculationMode(row: Record | null, mode: Exclude) {
+ const summary = detail.value?.summary
+ const measurement = measurementDetail.value
+ if (!summary?.inboundId || !measurement?.measurementId || measurement.version == null || !can('platform:firstleg:inbound:measure:edit')) return
+ const itemId = normalizedIdentifier(row?.id)
+ const itemNo = normalizedIdentifier(row?.itemNo)
+ const targetIds = new Set(measurement.items
+ .filter((item) => !row || (itemId ? String(item.id) === itemId : normalizedIdentifier(item.itemNo) === itemNo))
+ .map((item) => String(item.id)))
+ if (!targetIds.size) {
+ ElMessage.warning('当前数据尚未关联实测明细,暂不能设置计算方式')
+ return
+ }
+ measurementStrategySavingId.value = row ? (itemId || `item-${itemNo}`) : 'all'
+ try {
+ await Promise.all([loadMeasurementChargeRules(), loadMeasurementDimensionConfig()])
+ const activeRule = measurementChargeRules.value.find((rule) => rule.defaultRule && rule.status === 'ACTIVE')
+ const requiresMultiply = measurement.items.some((item) => (
+ targetIds.has(String(item.id)) ? mode : item.coefficientMode
+ ) === 'MULTIPLY')
+ if (requiresMultiply && !activeRule) {
+ ElMessage.warning('该客户尚未启用系数配置,请先到客户计费规则中设置默认规则')
+ return
+ }
+ await saveInboundMeasurementDraft(String(summary.tenantId), String(summary.inboundId), {
+ measurementId: String(measurement.measurementId),
+ version: measurement.version,
+ actualGoodsName: measurement.actualGoodsName,
+ items: measurement.items.map((item) => ({
+ inboundDetailId: item.inboundDetailId,
+ boxCode: item.boxCode,
+ itemNo: item.itemNo,
+ boxSpecification: item.boxSpecification,
+ packageCount: item.packageCount,
+ lengthCm: item.lengthCm,
+ widthCm: item.widthCm,
+ heightCm: item.heightCm,
+ unitWeightKg: item.unitWeightKg,
+ intelligentImageFileId: item.intelligentImageFileId,
+ machineNo: item.machineNo,
+ measuredAt: item.deviceMeasuredAt,
+ overLimitWarning: item.overLimitWarning,
+ outerLengthCm: item.outerLengthCm,
+ outerWidthCm: item.outerWidthCm,
+ outerHeightCm: item.outerHeightCm,
+ chargeRuleId: (targetIds.has(String(item.id)) ? mode : item.coefficientMode) === 'MULTIPLY'
+ ? activeRule!.id
+ : null,
+ coefficientMode: targetIds.has(String(item.id)) ? mode : item.coefficientMode || 'ADD',
+ remark: item.remark,
+ })),
+ })
+ measurementLoadedKey.value = ''
+ await loadMeasurementDetails()
+ ElMessage.success(`${row ? '当前数据' : '全部测量数据'}的计算方式已更新,体积已重新计算`)
+ } catch (error) {
+ measurementLoadedKey.value = ''
+ await loadMeasurementDetails()
+ ElMessage.error(getApiErrorDetails(error, '计算方式更新失败').message)
+ } finally {
+ measurementStrategySavingId.value = ''
+ }
+}
+
+async function saveWarehouseOuterDimensions(row: Record) {
+ const summary = detail.value?.summary
+ const measurement = measurementDetail.value
+ const editableMeasurementStatuses = new Set(['WAIT_MEASURE', 'MEASURING', 'MEASURE_PENDING', 'MEASURE_REJECTED'])
+ const currentStatus = editableMeasurementStatuses.has(measurement?.processStatus || '')
+ ? measurement!.processStatus
+ : summary?.mainStatus || measurement?.processStatus || ''
+ if (measurement && !editableMeasurementStatuses.has(currentStatus)) {
+ const statusLabel = orderStatusLabel(summary?.mainStatus) || measurementStatusLabel(currentStatus)
+ ElMessage.warning(`当前状态为“${statusLabel}”,该状态不可以修改测量数据`)
+ return
+ }
+ if (!summary?.inboundId || !measurement?.measurementId || measurement.version == null
+ || !can('platform:firstleg:inbound:measure:edit')) return
+ const dimensions = [row.outerLengthCm, row.outerWidthCm, row.outerHeightCm].map(Number)
+ if (dimensions.some((value) => !Number.isFinite(value) || value <= 0)) {
+ ElMessage.warning('仓库修改外箱长、宽、高必须同时填写且大于 0')
+ return
+ }
+ measurementOuterSavingId.value = String(row.id)
+ try {
+ await saveInboundMeasurementDraft(String(summary.tenantId), String(summary.inboundId), {
+ measurementId: String(measurement.measurementId),
+ version: measurement.version,
+ actualGoodsName: measurement.actualGoodsName,
+ items: measurement.items.map((item) => ({
+ inboundDetailId: item.inboundDetailId,
+ boxCode: item.boxCode,
+ itemNo: item.itemNo,
+ boxSpecification: item.boxSpecification,
+ packageCount: item.packageCount,
+ lengthCm: item.lengthCm,
+ widthCm: item.widthCm,
+ heightCm: item.heightCm,
+ unitWeightKg: item.unitWeightKg,
+ intelligentImageFileId: item.intelligentImageFileId,
+ machineNo: item.machineNo,
+ measuredAt: item.deviceMeasuredAt,
+ overLimitWarning: item.overLimitWarning,
+ outerLengthCm: String(item.id) === String(row.id) ? dimensions[0] : item.outerLengthCm,
+ outerWidthCm: String(item.id) === String(row.id) ? dimensions[1] : item.outerWidthCm,
+ outerHeightCm: String(item.id) === String(row.id) ? dimensions[2] : item.outerHeightCm,
+ chargeRuleId: item.chargeRuleId,
+ coefficientMode: item.coefficientMode || 'ADD',
+ remark: item.remark,
+ })),
+ })
+ measurementLoadedKey.value = ''
+ await loadMeasurementDetails()
+ ElMessage.success(`序号 ${row.itemNo || '—'} 的外箱规已更新,相关数值已重新计算`)
+ } catch (error) {
+ measurementLoadedKey.value = ''
+ await loadMeasurementDetails()
+ ElMessage.error(getApiErrorDetails(error, '仓库外箱尺寸保存失败').message)
+ } finally {
+ measurementOuterSavingId.value = ''
+ }
+}
+
+function measurementChargeRuleName(ruleId: unknown) {
+ const normalizedRuleId = normalizedIdentifier(ruleId)
+ const selected = normalizedRuleId
+ ? measurementChargeRules.value.find((rule) => rule.id === normalizedRuleId)
+ : null
+ const fallback = measurementChargeRules.value.find((rule) => rule.defaultRule && rule.status === 'ACTIVE')
+ || measurementChargeRules.value.find((rule) => rule.status === 'ACTIVE')
+ const rule = selected || fallback
+ if (rule) {
+ const remark = rule.remark?.trim()
+ return `系数 ${rule.lengthFactor}${remark ? ` · ${remark}` : ''}${rule.defaultRule ? '(默认)' : ''}`
+ }
+ if (measurementChargeRulesLoading.value) return '加载中…'
+ return normalizedRuleId || '未配置'
+}
+
+async function loadMeasurementChargeRules() {
+ const tenantId = normalizedIdentifier(detail.value?.summary.tenantId)
+ const customerId = normalizedIdentifier(detail.value?.summary.customerId) || tenantId
+ const loadedKey = `${tenantId}:${customerId}`
+ if (!customerId || measurementChargeRulesLoadedKey.value === loadedKey || measurementChargeRulesLoading.value) return
+ measurementChargeRulesLoading.value = true
+ try {
+ measurementChargeRules.value = await listCustomerChargeRules(customerId, tenantId)
+ measurementChargeRulesLoadedKey.value = loadedKey
+ } catch {
+ measurementChargeRules.value = []
+ } finally {
+ measurementChargeRulesLoading.value = false
+ }
+}
+
+async function loadMeasurementDimensionConfig() {
+ const tenantId = normalizedIdentifier(detail.value?.summary.tenantId)
+ if (!tenantId || measurementDimensionConfigLoadedKey.value === tenantId || measurementDimensionConfigLoading.value) return
+ measurementDimensionConfigLoading.value = true
+ try {
+ measurementDimensionConfig.value = await getCustomerDimensionConfig(tenantId)
+ measurementDimensionConfigLoadedKey.value = tenantId
+ } catch {
+ measurementDimensionConfig.value = null
+ } finally {
+ measurementDimensionConfigLoading.value = false
+ }
+}
+
interface MeasurementTotals {
count: number
gross: number
@@ -608,8 +968,16 @@ async function loadMeasurementDetails() {
measurementLoading.value = true
measurementError.value = null
try {
- const response = await getInboundMeasurement(String(summary.tenantId), String(summary.inboundId))
+ const [response] = await Promise.all([
+ getInboundMeasurement(String(summary.tenantId), String(summary.inboundId)),
+ loadMeasurementChargeRules(),
+ loadMeasurementDimensionConfig(),
+ ])
if (serial !== measurementRequestSerial) return
+ const configuredMode = measurementDimensionConfig.value?.defaultCalculationMode
+ measurementSectionModes.outer = configuredMode === 'MULTIPLY' && activeMeasurementChargeRule.value
+ ? 'MULTIPLY'
+ : 'ADD'
measurementDetail.value = response
measurementLoadedKey.value = key
} catch (error) {
@@ -837,6 +1205,64 @@ function openMeasurement(row: UnifiedOrderResource) {
measurementDialogOpen.value = true
}
+function openFeeConfirmDialog() {
+ const summary = detail.value?.summary
+ const reviewStatus = String(summary?.measurement?.reviewStatus ?? '').trim().toUpperCase()
+ if (reviewStatus !== 'APPROVED') {
+ ElMessage.warning('测量审核通过后才能确认并发送费用')
+ return
+ }
+ if (!summary?.inboundId) {
+ ElMessage.warning('当前单据缺少入仓信息,暂不能发送费用')
+ return
+ }
+ if (confirmFeeSending.value) {
+ return
+ }
+ feeConfirmWeight.value = Number(summary.measurement?.actualGrossWeightKg) || null
+ feeConfirmVolume.value = Number(summary.measurement?.actualVolumeCbm) || null
+ feeConfirmOpen.value = true
+}
+
+async function executeConfirmAndSendFee() {
+ const summary = detail.value?.summary
+ if (!summary?.inboundId) return
+ const selectedWeightKg = Number(feeConfirmWeight.value)
+ const selectedVolumeCbm = Number(feeConfirmVolume.value)
+ if (!(selectedWeightKg > 0) || !(selectedVolumeCbm > 0)) {
+ ElMessage.warning('计费毛重和计费体积必须大于 0')
+ return
+ }
+ confirmFeeSending.value = true
+ try {
+ const tenantId = String(summary.tenantId)
+ const inboundId = String(summary.inboundId)
+ let snapshot = await getLatestInboundFee(tenantId, inboundId)
+ if (snapshot.feeStatus === 'CALCULATED') {
+ snapshot = await calculateInboundFee(tenantId, inboundId, {
+ selectedWeightKg,
+ selectedVolumeCbm,
+ weightSource: 'ACTUAL',
+ volumeSource: 'ACTUAL',
+ }, createFeeActionIdempotencyKey('calculate'))
+ snapshot = await confirmInboundFee(tenantId, inboundId, createFeeActionIdempotencyKey('confirm'))
+ }
+ if (snapshot.feeStatus === 'CONFIRMED') {
+ await sendInboundFee(tenantId, inboundId, createFeeActionIdempotencyKey('send'))
+ ElMessage.success('费用已确认并发送给客户')
+ feeConfirmOpen.value = false
+ await loadDetail(true)
+ return
+ }
+ if (snapshot.feeStatus === 'SENT') ElMessage.info('该费用已经发送给客户,无需重复发送')
+ else ElMessage.warning('当前没有可发送的试算费用,请先在费用信息中完成费用试算')
+ } catch (error) {
+ ElMessage.error(getApiErrorDetails(error, '确认费用发送失败').message)
+ } finally {
+ confirmFeeSending.value = false
+ }
+}
+
function handleOrderSelectionChange(rows: UnifiedOrderResource[]) {
selectedOrders.value = rows
selectedApprovalOrders.value = rows.filter(canApprove)
@@ -1021,6 +1447,7 @@ function handleMeasurementSubmitted() {
function clearMeasurementOrder() {
measurementOrder.value = null
+ openWarehouseSuggestionOnMeasurementLoad.value = false
}
type UnifiedOrderRowAction = 'note' | 'approve' | 'measurement'
@@ -1163,6 +1590,17 @@ async function loadDetail(force = false) {
measurementDetail.value = null
measurementError.value = null
measurementLoadedKey.value = ''
+ measurementSectionModes.customer = 'ACTUAL'
+ measurementSectionModes.outer = 'ACTUAL'
+ measurementSectionModes.actual = 'ACTUAL'
+ if (measurementChargeRulesLoadedKey.value !== tenantId) {
+ measurementChargeRules.value = []
+ measurementChargeRulesLoadedKey.value = ''
+ }
+ if (measurementDimensionConfigLoadedKey.value !== tenantId) {
+ measurementDimensionConfig.value = null
+ measurementDimensionConfigLoadedKey.value = ''
+ }
measurementPreviewOpen.value = false
releaseMeasurementPreview()
releaseCargoImagePreviews()
@@ -1226,6 +1664,22 @@ onBeforeUnmount(() => {
+
+
+
+
+ kg
+ m³
+
+ 参考数据
+
+
+
+ {{ row.weight == null ? '—' : formatNumber(row.weight, 2) }}
+ {{ row.volume == null ? '—' : formatNumber(row.volume, 4) }}
+ {{ row.volume == null ? '—' : formatNumber(Number(row.volume) * 167, 2) }}
+
+
- 测量数据 {{ detail.summary.measurement?.completedAt ? `完成于 ${formatDate(detail.summary.measurement.completedAt)}` : '尚未完成测量' }}
+
+
测量数据 {{ detail.summary.measurement?.completedAt ? `完成于 ${formatDate(detail.summary.measurement.completedAt)}` : '尚未完成测量' }}
+
+ 确认费用发送
+
+
@@ -1428,28 +1894,50 @@ onBeforeUnmount(() => {
- 一、客户下单数据 总件数:{{ measurementTotals(detail.order.cargoes, true).count ?? '—' }} 总毛重:{{ formatNumber(measurementTotals(detail.order.cargoes, true).gross) }} 总体积:{{ formatNumber(measurementTotals(detail.order.cargoes, true).volume, 6) }} 总材积重:{{ formatNumber(measurementTotals(detail.order.cargoes, true).volumetric) }} 重量体积:{{ formatNumber(measurementTotals(detail.order.cargoes, true).weightVolume, 6) }}
+ 一、客户下单数据 总件数:{{ measurementTotals(customerMeasurementRows).count ?? '—' }} 总毛重:{{ formatNumber(measurementTotals(customerMeasurementRows).gross) }} 总体积:{{ formatNumber(measurementTotals(customerMeasurementRows).volume, 6) }} 总材积重:{{ formatNumber(measurementTotals(customerMeasurementRows).volumetric) }} 重量体积:{{ formatNumber(measurementTotals(customerMeasurementRows).weightVolume, 6) }}
-
{{ measurementValue(row as Record, column.key, true) }}
-
合计 箱数:{{ measurementTotals(detail.order.cargoes, true).count ?? '—' }} 总毛重:{{ formatNumber(measurementTotals(detail.order.cargoes, true).gross) }} 总体积:{{ formatNumber(measurementTotals(detail.order.cargoes, true).volume, 6) }} 总材积:{{ formatNumber(measurementTotals(detail.order.cargoes, true).volumetric) }}
+
{{ measurementValue(row as Record, column.key) }}
+
合计 箱数:{{ measurementTotals(customerMeasurementRows).count ?? '—' }} 总毛重:{{ formatNumber(measurementTotals(customerMeasurementRows).gross) }} 总体积:{{ formatNumber(measurementTotals(customerMeasurementRows).volume, 6) }} 总材积:{{ formatNumber(measurementTotals(customerMeasurementRows).volumetric) }}
- 二、外箱规数据 总件数:{{ measurementTotals((measurementDetail?.items || []) as unknown as Record[]).count ?? '—' }} 总毛重:{{ formatNumber(measurementTotals((measurementDetail?.items || []) as unknown as Record[]).gross) }} 总体积:{{ formatNumber(measurementTotals((measurementDetail?.items || []) as unknown as Record[]).volume, 6) }} 总材积重:{{ formatNumber(measurementTotals((measurementDetail?.items || []) as unknown as Record[]).volumetric) }} 重量体积:{{ formatNumber(measurementTotals((measurementDetail?.items || []) as unknown as Record[]).weightVolume, 6) }}
+ 二、外箱规数据 总件数:{{ measurementTotals(outerMeasurementRows).count ?? '—' }} 总毛重:{{ formatNumber(measurementTotals(outerMeasurementRows).gross) }} 总体积:{{ formatNumber(measurementTotals(outerMeasurementRows).volume, 6) }} 总材积重:{{ formatNumber(measurementTotals(outerMeasurementRows).volumetric) }} 重量体积:{{ formatNumber(measurementTotals(outerMeasurementRows).weightVolume, 6) }}
-
{{ measurementValue(row as Record, column.key) }}
-
合计 箱数:{{ measurementTotals((measurementDetail?.items || []) as unknown as Record[]).count ?? '—' }} 总毛重:{{ formatNumber(measurementTotals((measurementDetail?.items || []) as unknown as Record[]).gross) }} 总体积:{{ formatNumber(measurementTotals((measurementDetail?.items || []) as unknown as Record[]).volume, 6) }} 总材积:{{ formatNumber(measurementTotals((measurementDetail?.items || []) as unknown as Record[]).volumetric) }}
+
{{ measurementValue(row as Record, column.key) }}
+
合计 箱数:{{ measurementTotals(outerMeasurementRows).count ?? '—' }} 总毛重:{{ formatNumber(measurementTotals(outerMeasurementRows).gross) }} 总体积:{{ formatNumber(measurementTotals(outerMeasurementRows).volume, 6) }} 总材积:{{ formatNumber(measurementTotals(outerMeasurementRows).volumetric) }}
+
+ 箱规计算规则选择
+
+
+
+
+
+ 规则参数来源:客户管理
+
- 三、实际测量数据 总件数:{{ measurementDetail?.actualPackageCount ?? '—' }} 总毛重:{{ formatNumber(measurementDetail?.actualGrossWeightKg) }} 总体积:{{ formatNumber(measurementDetail?.actualVolumeCbm, 6) }} 总材积重:{{ formatNumber(measurementDetail?.volumeWeightKg) }} 重量体积:{{ formatNumber(measurementDetail?.actualVolumeCbm, 6) }}
+ 三、实际测量数据 总件数:{{ measurementTotals(actualMeasurementRows).count ?? '—' }} 总毛重:{{ formatNumber(measurementTotals(actualMeasurementRows).gross) }} 总体积:{{ formatNumber(measurementTotals(actualMeasurementRows).volume, 6) }} 总材积重:{{ formatNumber(measurementTotals(actualMeasurementRows).volumetric) }} 重量体积:{{ formatNumber(measurementTotals(actualMeasurementRows).weightVolume, 6) }}
-
+
{{ row.boxCode || '—' }}
-
+
+
+ {{ formatNumber(row.outerLengthCm) }}
+
+
+ {{ formatNumber(row.outerWidthCm) }}
+
+
+ {{ formatNumber(row.outerHeightCm) }}
+
+
+ )">保存外箱
+
+
{{ measurementValue(row as Record, column.key) }}
@@ -1466,7 +1954,7 @@ onBeforeUnmount(() => {
-
合计 箱数:{{ measurementDetail?.actualPackageCount ?? '—' }} 总毛重:{{ formatNumber(measurementDetail?.actualGrossWeightKg) }} 总体积:{{ formatNumber(measurementDetail?.actualVolumeCbm, 6) }} 总材积:{{ formatNumber(measurementDetail?.volumeWeightKg) }}
+
合计 箱数:{{ measurementTotals(actualMeasurementRows).count ?? '—' }} 总毛重:{{ formatNumber(measurementTotals(actualMeasurementRows).gross) }} 总体积:{{ formatNumber(measurementTotals(actualMeasurementRows).volume, 6) }} 总材积:{{ formatNumber(measurementTotals(actualMeasurementRows).volumetric) }}
称重图片与确认重量 称重图片独立展示;重量值直接采用服务端确认结果。
@@ -1670,6 +2158,7 @@ onBeforeUnmount(() => {
:customer-id="String(measurementOrder.tenantId)"
:inbound-id="String(measurementOrder.inboundId)"
:inbound-code="measurementOrder.inboundCode"
+ :open-warehouse-suggestion="openWarehouseSuggestionOnMeasurementLoad"
@updated="handleMeasurementUpdated"
@submitted="handleMeasurementSubmitted"
@closed="clearMeasurementOrder"
@@ -1765,6 +2254,15 @@ onBeforeUnmount(() => {
.panel-title h2 { margin: 0; color: #344054; font-size: 16px; }
.panel-title p { margin: 5px 0 0; color: var(--cc-muted); font-size: 11px; }
.panel-title > svg { color: var(--cc-primary); }
+.measurement-title-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
+.measurement-title-actions :deep(.el-button) { margin-left: 0; }
+.fee-confirm-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 22px; }
+.fee-confirm-number-wrap { position: relative; width: 100%; }
+.fee-confirm-number { width: 100%; }
+.fee-confirm-number :deep(.el-input__wrapper) { padding-right: 42px; }
+.fee-confirm-unit { position: absolute; top: 50%; right: 12px; z-index: 1; transform: translateY(-50%); color: var(--cc-muted); pointer-events: none; }
+.fee-confirm-reference-title { margin: 8px 0 12px; color: var(--cc-text); font-size: 14px; }
+@media (max-width: 680px) { .fee-confirm-form { grid-template-columns: 1fr; } .fee-confirm-unit { right: 12px; } }
.fee-section-title { margin: 14px 0 8px; color: #344054; font-size: 13px; line-height: 1.4; }
.fee-overview { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 8px 0 0; border: 1px solid var(--cc-border); }
.fee-overview > div { display: grid; grid-template-columns: auto 1fr; align-items: baseline; gap: 4px 12px; padding: 10px 12px; }
@@ -1783,13 +2281,19 @@ onBeforeUnmount(() => {
.cargo-image { width: 72px; height: 56px; border: 1px solid var(--cc-border); border-radius: 6px; background: #f8fafc; }
.measurement-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; margin: 0; border: 1px solid var(--cc-border); background: var(--cc-border); }
.measurement-section { margin-top: 18px; }
-.measurement-section-title { display: flex; width: 100%; min-height: 48px; align-items: center; gap: 16px; padding: 9px 14px; border: 1px solid var(--cc-border); border-radius: 7px 7px 0 0; background: #f8fafc; color: var(--cc-text); font: inherit; text-align: left; cursor: pointer; }
+.measurement-section-title { display: flex; width: 100%; min-height: 48px; align-items: center; gap: 12px; padding: 7px 12px; border: 1px solid var(--cc-border); border-radius: 7px 7px 0 0; background: #f8fafc; color: var(--cc-text); }
.measurement-section-title:hover { background: #f2f6fb; }
-.measurement-section-title:focus-visible { outline: 2px solid var(--cc-primary); outline-offset: 2px; }
-.measurement-section-title[aria-expanded='false'] { border-radius: 7px; }
.measurement-section-title h3 { flex: 0 0 auto; margin: 0; color: var(--cc-text); font-size: 14px; }
.measurement-section-summary { display: flex; min-width: 0; flex: 1; align-items: center; justify-content: flex-end; gap: 18px; color: var(--cc-muted); font-size: 12px; white-space: nowrap; }
-.measurement-section-title > svg { flex: 0 0 auto; color: var(--cc-muted); }
+.measurement-section-toggle { display: flex; min-width: 0; flex: 1; align-items: center; gap: 16px; padding: 2px; border: 0; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }
+.measurement-section-toggle:focus-visible { outline: 2px solid var(--cc-primary); outline-offset: 2px; }
+.measurement-section-toggle > svg { flex: 0 0 auto; color: var(--cc-muted); }
+.measurement-section-mode { flex: 0 0 190px; }
+.measurement-section-title > .measurement-section-mode { display: none; }
+.measurement-rule-selector { display: flex; align-items: center; gap: 14px; margin: 8px 0 12px; padding: 12px 16px; border: 1px solid var(--cc-border); border-radius: 7px; background: #f8fafc; }
+.measurement-rule-selector__label { color: var(--cc-text); font-size: 12px; font-weight: 650; white-space: nowrap; }
+.measurement-rule-selector__control { width: 270px; }
+.measurement-rule-selector__hint { color: var(--cc-muted); font-size: 11px; }
.measurement-total { display: flex; min-height: 42px; align-items: center; justify-content: flex-end; gap: 24px; padding: 9px 14px; border: 1px solid var(--cc-border); background: #f8fafc; color: var(--cc-muted); font-size: 12px; }
.measurement-total { border-top: 0; border-radius: 0 0 7px 7px; }
.measurement-total strong { margin-right: auto; color: var(--cc-text); font-size: 13px; }
@@ -1824,5 +2328,6 @@ onBeforeUnmount(() => {
@media (max-width: 720px) { .page-intro { align-items: stretch; flex-direction: column; } .page-intro :deep(.el-button) { width: 100%; min-height: 44px; } .status-tab { min-height: 46px; padding: 0 14px; } .table-actions :deep(.el-button) { width: 100%; min-height: 40px; } .row-actions { flex-wrap: wrap; } .order-detail-header { align-items: stretch; flex-direction: column; } .order-detail-heading { display: grid; grid-template-columns: auto 36px minmax(0, 1fr) auto; } .detail-header-actions { display: grid; grid-template-columns: 1fr; } .detail-header-actions :deep(.el-button) { min-height: 40px; } .detail-overview, .order-info-grid, .measurement-grid { grid-template-columns: 1fr; } .detail-overview article { border-right: 0; border-bottom: 1px solid var(--cc-border); } .detail-overview article:last-child { border-bottom: 0; } .order-info-grid > div.wide { grid-column: auto; } .detail-tabs button { padding: 0 18px; } .detail-panel { padding: 16px; } .measurement-section-title { align-items: flex-start; flex-wrap: wrap; padding: 12px; } .measurement-section-summary { width: 100%; flex-basis: calc(100% - 34px); flex-wrap: wrap; justify-content: flex-start; gap: 6px 16px; white-space: normal; } .measurement-section-title > svg { margin-left: auto; } .measurement-total { flex-wrap: wrap; justify-content: flex-start; gap: 6px 16px; } .measurement-total strong { width: 100%; } }
@media (max-width: 720px) { .weight-evidence__heading { flex-direction: column; } .weight-evidence__heading dl { text-align: left; } }
@media (max-width: 720px) { .fee-overview { grid-template-columns: 1fr; } .fee-overview > div + div { border-top: 1px solid var(--cc-border); border-left: 0; } }
+@media (max-width: 720px) { .measurement-section-title { align-items: stretch; flex-direction: column; } .measurement-section-toggle { align-items: flex-start; flex-wrap: wrap; } .measurement-section-toggle > svg { margin-left: auto; } .measurement-section-mode { width: 100%; flex-basis: auto; } }
@media (max-width: 420px) { .order-detail-heading { grid-template-columns: auto minmax(0, 1fr) auto; } .order-mark { display: none; } .weight-evidence { padding: 10px; } .weight-evidence-grid { grid-template-columns: 1fr; } }