@@ -0,0 +1,641 @@
< script setup lang = "ts" >
import { computed , onMounted , reactive , ref , watch } from 'vue'
import { ElMessage , ElMessageBox , type FormInstance , type FormRules } from 'element-plus'
import { CalendarClock , Eye , Plus , Power , Ship , Warehouse } from '@lucide/vue'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
import { listPlatformTenants , type PlatformTenantResource as PlatformCustomerResource } from '@/modules/iam'
import { listPlatformWarehouses , type PlatformWarehouseResource } from '@/modules/warehouses'
import { listPlatformPartners , type PlatformPartnerResource } from '@/modules/master-data'
import { getApiErrorDetails , type ApiErrorDetails } from '@/shared/api/http'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import AppDrawer from '@/shared/components/AppDrawer.vue'
import DataTable , { type DataTableColumn } from '@/shared/components/DataTable.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import QueryToolbar , { type QueryField , type QueryModel } from '@/shared/components/QueryToolbar.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
import {
createPlatformCarrierRateVersion ,
getPlatformCarrierRate ,
listPlatformCarrierRates ,
retirePlatformCarrierRate ,
type CarrierRateChargeUnit ,
type CarrierRateStatus ,
type CreatePlatformCarrierRateVersionRequest ,
type PlatformCarrierRateResource ,
} from '../api/platformCarrierRates'
type RateFilters = QueryModel & {
customerId : string
rateCode : string
destinationCode : string
channelCode : string
chargeUnit : CarrierRateChargeUnit | ''
status : CarrierRateStatus | ''
}
interface RateForm {
rateCode : string
originWarehouseId : string
businessType : string
channelCode : string
carrierId : string
destinationCode : string
chargeUnit : CarrierRateChargeUnit
unitPrice : number | null
currency : string
additionalFee : number | null
effectiveFrom : string
effectiveTo : string
remark : string
}
const access = useAccessPreviewStore ( )
const customers = ref < PlatformCustomerResource [ ] > ( [ ] )
const customersLoading = ref ( false )
const customerLoadError = ref < ApiErrorDetails | null > ( null )
const loadedCustomerId = ref < string | null > ( null )
const rows = ref < PlatformCarrierRateResource [ ] > ( [ ] )
const warehouses = ref < PlatformWarehouseResource [ ] > ( [ ] )
const carriers = ref < PlatformPartnerResource [ ] > ( [ ] )
const loading = ref ( false )
const masterDataLoading = ref ( false )
const loadError = ref < ApiErrorDetails | null > ( null )
const page = ref ( 1 )
const pageSize = ref ( 20 )
const editorOpen = ref ( false )
const detailOpen = ref ( false )
const detailLoading = ref ( false )
const saving = ref ( false )
const detail = ref < PlatformCarrierRateResource | null > ( null )
const retiringIds = ref ( new Set < string > ( ) )
const editorCustomerId = ref ( '' )
const formRef = ref < FormInstance > ( )
let loadSequence = 0
let masterDataSequence = 0
const emptyForm = ( ) : RateForm => ( {
rateCode : '' ,
originWarehouseId : '' ,
businessType : '' ,
channelCode : '' ,
carrierId : '' ,
destinationCode : '' ,
chargeUnit : 'KG' ,
unitPrice : null ,
currency : 'CNY' ,
additionalFee : 0 ,
effectiveFrom : '' ,
effectiveTo : '' ,
remark : '' ,
} )
const form = reactive < RateForm > ( emptyForm ( ) )
const editorLabel = computed ( ( ) => form . rateCode ? ` 创建 ${ form . rateCode } 的新版本 ` : '新增船司 FBA 价格版本' )
const requiredPermission = computed ( ( ) => 'firstleg:fba-pricing:rate:edit' )
const { captureBaseline } = useUnsavedWorkRegistration ( {
key : 'carrier-rate-version-editor' ,
label : editorLabel ,
isOpen : editorOpen ,
model : form ,
requiredPermission ,
} )
const filters = ref < RateFilters > ( { customerId : '' , rateCode : '' , destinationCode : '' , channelCode : '' , chargeUnit : '' , status : '' } )
const appliedFilters = ref < RateFilters > ( { ... filters . value } )
const customerSwitchDisabled = computed ( ( ) => editorOpen . value || detailOpen . value || saving . value || detailLoading . value || retiringIds . value . size > 0 )
const queryFields = computed < QueryField [ ] > ( ( ) => [
{
key : 'customerId' ,
type : 'select' ,
width : 280 ,
placeholder : customerLoadError . value ? '企业列表加载失败' : '全部客户' ,
props : { 'aria-label' : '所属企业' , filterable : true , disabled : customersLoading . value || Boolean ( customerLoadError . value ) || customerSwitchDisabled . value } ,
options : customers . value . map ( ( customer ) => ( { value : String ( customer . id ) , label : ` ${ customer . tenantName } ( ${ customer . tenantCode || customer . id } ) ` } ) ) ,
} ,
{ key : 'rateCode' , type : 'input' , width : 220 , placeholder : '价格编码' , props : { 'aria-label' : '价格编码' } } ,
{ key : 'destinationCode' , type : 'input' , width : 220 , placeholder : 'FBA 目的仓编码' , props : { 'aria-label' : 'FBA 目的仓编码' } } ,
{ key : 'channelCode' , type : 'input' , width : 220 , placeholder : '渠道编码' , props : { 'aria-label' : '渠道编码' } } ,
{
key : 'chargeUnit' , type : 'select' , width : 180 , placeholder : '全部计费单位' , props : { 'aria-label' : '计费单位' } , options : [
{ label : 'KG' , value : 'KG' } ,
{ label : 'CBM' , value : 'CBM' } ,
] ,
} ,
{
key : 'status' , type : 'select' , width : 160 , placeholder : '全部状态' , props : { 'aria-label' : '价格状态' } , options : [
{ label : '生效中' , value : 'ACTIVE' } ,
{ label : '已停用' , value : 'INACTIVE' } ,
] ,
} ,
] )
const columns : DataTableColumn [ ] = [
{ key : 'actions' , label : '操作' , width : 230 , fixed : 'left' , slotName : 'actions' } ,
{ key : 'customer' , label : '所属企业' , minWidth : 200 , slotName : 'customer' } ,
{ key : 'destinationCode' , prop : 'destinationCode' , label : 'FBA 目的仓' , minWidth : 135 } ,
{ key : 'rate' , label : '价格版本' , minWidth : 180 , slotName : 'rate' } ,
{ key : 'originWarehouseName' , prop : 'originWarehouseName' , label : '起运仓' , minWidth : 170 , showOverflowTooltip : true } ,
{ key : 'channelCode' , prop : 'channelCode' , label : '渠道编码' , minWidth : 150 , showOverflowTooltip : true } ,
{ key : 'carrier' , label : '船司' , minWidth : 170 , slotName : 'carrier' } ,
{ key : 'businessType' , prop : 'businessType' , label : '业务类型' , minWidth : 110 } ,
{ key : 'chargeUnit' , prop : 'chargeUnit' , label : '计费单位' , width : 95 } ,
{ key : 'unitPrice' , label : '单价' , minWidth : 135 , slotName : 'unitPrice' } ,
{ key : 'additionalFee' , label : '固定附加费' , minWidth : 135 , slotName : 'additionalFee' } ,
{ key : 'currency' , prop : 'currency' , label : '币种' , width : 80 } ,
{ key : 'period' , label : '有效期' , minWidth : 210 , slotName : 'period' } ,
{ key : 'remark' , prop : 'remark' , label : '备注' , minWidth : 190 , showOverflowTooltip : true , formatter : ( row : PlatformCarrierRateResource ) => row . remark || '-' } ,
{ key : 'status' , label : '状态' , width : 90 , slotName : 'status' } ,
{ key : 'updatedAt' , label : '更新时间' , minWidth : 150 , formatter : ( row : PlatformCarrierRateResource ) => formatDateTime ( row . updatedAt ) } ,
]
const originWarehouses = computed ( ( ) => warehouses . value . filter ( ( item ) => item . status === 'ACTIVE' && item . warehouseType === 'DOMESTIC' ) )
const fbaWarehouses = computed ( ( ) => warehouses . value . filter ( ( item ) => item . status === 'ACTIVE' && item . warehouseType === 'FBA' ) )
const activeCarriers = computed ( ( ) => carriers . value . filter ( ( item ) => item . status === 'ACTIVE' ) )
const filteredRows = computed ( ( ) => rows . value . filter ( ( row ) => {
const code = appliedFilters . value . rateCode . trim ( ) . toLowerCase ( )
const destinationCode = appliedFilters . value . destinationCode . trim ( ) . toLowerCase ( )
const channelCode = appliedFilters . value . channelCode . trim ( ) . toLowerCase ( )
return ( ! code || row . rateCode . toLowerCase ( ) . includes ( code ) )
&& ( ! destinationCode || row . destinationCode . toLowerCase ( ) . includes ( destinationCode ) )
&& ( ! channelCode || row . channelCode . toLowerCase ( ) . includes ( channelCode ) )
&& ( ! appliedFilters . value . chargeUnit || row . chargeUnit === appliedFilters . value . chargeUnit )
&& ( ! appliedFilters . value . status || row . status === appliedFilters . value . status )
} ) )
const pagedRows = computed ( ( ) => filteredRows . value . slice ( ( page . value - 1 ) * pageSize . value , page . value * pageSize . value ) )
const activeCount = computed ( ( ) => rows . value . filter ( ( row ) => row . status === 'ACTIVE' ) . length )
const loadErrorMessage = computed ( ( ) => loadError . value ? . message || '' )
function validateEffectiveTo ( _rule : unknown , value : string , callback : ( error ? : Error ) => void ) {
if ( ! value || ! form . effectiveFrom || new Date ( value ) . getTime ( ) > new Date ( form . effectiveFrom ) . getTime ( ) ) callback ( )
else callback ( new Error ( '结束时间必须晚于开始时间' ) )
}
const rules : FormRules < RateForm > = {
rateCode : [ { required : true , whitespace : true , message : '请输入价格编码' , trigger : 'blur' } ] ,
originWarehouseId : [ { required : true , message : '请选择起运仓' , trigger : 'change' } ] ,
businessType : [ { required : true , whitespace : true , message : '请输入业务类型' , trigger : 'blur' } ] ,
channelCode : [ { required : true , whitespace : true , message : '请输入渠道编码' , trigger : 'blur' } ] ,
carrierId : [ { required : true , message : '请选择船司' , trigger : 'change' } ] ,
destinationCode : [ { required : true , whitespace : true , message : '请选择或输入 FBA 目的仓编码' , trigger : 'change' } ] ,
chargeUnit : [ { required : true , message : '请选择计费单位' , trigger : 'change' } ] ,
unitPrice : [ { required : true , type : 'number' , min : 0.000001 , message : '单价必须大于 0' , trigger : 'change' } ] ,
currency : [
{ required : true , whitespace : true , message : '请输入币种' , trigger : 'blur' } ,
{ pattern : /^[A-Z]{3}$/ , message : '币种须为三个大写英文字母' , trigger : 'blur' } ,
] ,
additionalFee : [ { required : true , type : 'number' , min : 0 , message : '附加费不能小于 0' , trigger : 'change' } ] ,
effectiveFrom : [ { required : true , message : '请选择生效时间' , trigger : 'change' } ] ,
effectiveTo : [ { validator : validateEffectiveTo , trigger : 'change' } ] ,
}
watch ( ( ) => filters . value . customerId , ( customerId ) => {
if ( customerId === loadedCustomerId . value ) return
masterDataSequence += 1
warehouses . value = [ ]
carriers . value = [ ]
loadedCustomerId . value = null
page . value = 1
} )
watch (
( ) => access . can ( 'firstleg:fba-pricing:rate:edit' ) ,
( canEdit ) => {
if ( canEdit || ! editorOpen . value ) return
editorOpen . value = false
Object . assign ( form , emptyForm ( ) )
} ,
{ flush : 'sync' } ,
)
function formatDateTime ( value : string | null | undefined ) {
return value ? value . replace ( 'T' , ' ' ) . slice ( 0 , 16 ) : '-'
}
function formatMoney ( value : number , currency : string ) {
return ` ${ currency } ${ Number ( value ) . toLocaleString ( 'zh-CN' , { minimumFractionDigits : 2 , maximumFractionDigits : 4 } )} `
}
function statusLabel ( status : CarrierRateStatus ) {
return status === 'ACTIVE' ? '启用' : '停用'
}
function customerLabel ( customerId : string ) {
const customer = customers . value . find ( ( item ) => String ( item . id ) === String ( customerId ) )
return customer ? ` ${ customer . tenantName } ( ${ customer . tenantCode || '未设置编码' } ) ` : '客户信息待加载'
}
function nullable ( value : string ) {
return value . trim ( ) || null
}
async function loadCustomers ( ) {
customersLoading . value = true
customerLoadError . value = null
try {
const records : PlatformCustomerResource [ ] = [ ]
let currentPage = 1
let pages = 1
do {
const result = await listPlatformTenants ( { page : currentPage , size : 200 , status : 'ACTIVE' } )
records . push ( ... result . records )
pages = Math . max ( 1 , result . pages )
currentPage += 1
} while ( currentPage <= pages )
customers . value = records
} catch ( error ) {
customers . value = [ ]
customerLoadError . value = getApiErrorDetails ( error , '企业列表加载失败,暂时无法按所属企业筛选或显示企业名称;价格列表仍可查询' )
} finally {
customersLoading . value = false
}
}
async function loadRateData ( customerId = filters . value . customerId ) {
const sequence = ++ loadSequence
loading . value = true
loadError . value = null
try {
const rateResult = await listPlatformCarrierRates ( customerId ? { customerId } : { } )
if ( sequence !== loadSequence ) return
rows . value = rateResult
} catch ( error ) {
if ( sequence !== loadSequence ) return
rows . value = [ ]
loadError . value = getApiErrorDetails ( error , '船司 FBA 价格加载失败,请稍后重试' )
} finally {
if ( sequence === loadSequence ) {
loading . value = false
}
}
}
async function loadCustomerMasterData ( customerId : string ) {
if ( ! customerId ) return false
const sequence = ++ masterDataSequence
masterDataLoading . value = true
try {
const [ warehouseResult , partnerResult ] = await Promise . all ( [
listPlatformWarehouses ( customerId , 1 , 200 ) ,
listPlatformPartners ( customerId , { page : 1 , size : 200 , type : 'CARRIER' , status : 'ACTIVE' } ) ,
] )
if ( sequence !== masterDataSequence ) return false
warehouses . value = warehouseResult . records . map ( ( item ) => ( { ... item , id : String ( item . id ) , tenantId : String ( item . tenantId ) } ) )
carriers . value = partnerResult . records . map ( ( item ) => ( { ... item , id : String ( item . id ) , tenantId : String ( item . tenantId ) , dataScopeId : String ( item . dataScopeId ) } ) )
loadedCustomerId . value = customerId
return true
} catch ( error ) {
if ( sequence === masterDataSequence ) {
warehouses . value = [ ]
carriers . value = [ ]
loadedCustomerId . value = null
ElMessage . error ( getApiErrorDetails ( error , '所属企业的仓库或船司资料加载失败' ) . message )
}
return false
} finally {
if ( sequence === masterDataSequence ) masterDataLoading . value = false
}
}
async function applyQuery ( model : QueryModel ) {
const customerId = String ( model . customerId || '' )
appliedFilters . value = {
customerId ,
rateCode : String ( model . rateCode || '' ) ,
destinationCode : String ( model . destinationCode || '' ) ,
channelCode : String ( model . channelCode || '' ) ,
chargeUnit : ( model . chargeUnit || '' ) as CarrierRateChargeUnit | '' ,
status : ( model . status || '' ) as CarrierRateStatus | '' ,
}
page . value = 1
await Promise . all ( [
loadRateData ( customerId ) ,
customerId && loadedCustomerId . value !== customerId ? loadCustomerMasterData ( customerId ) : Promise . resolve ( true ) ,
] )
}
function resetQuery ( model : QueryModel ) {
appliedFilters . value = {
customerId : String ( model . customerId || '' ) ,
rateCode : String ( model . rateCode || '' ) ,
destinationCode : String ( model . destinationCode || '' ) ,
channelCode : String ( model . channelCode || '' ) ,
chargeUnit : ( model . chargeUnit || '' ) as CarrierRateChargeUnit | '' ,
status : ( model . status || '' ) as CarrierRateStatus | '' ,
}
page . value = 1
}
function resetForm ( rate ? : PlatformCarrierRateResource ) {
Object . assign ( form , emptyForm ( ) , rate ? {
rateCode : rate . rateCode ,
originWarehouseId : rate . originWarehouseId ,
businessType : rate . businessType ,
channelCode : rate . channelCode ,
carrierId : rate . carrierId ,
destinationCode : rate . destinationCode ,
chargeUnit : rate . chargeUnit ,
unitPrice : rate . unitPrice ,
currency : rate . currency ,
additionalFee : rate . additionalFee ,
effectiveFrom : '' ,
effectiveTo : '' ,
remark : rate . remark || '' ,
} : { } )
}
async function openCreate ( rate ? : PlatformCarrierRateResource ) {
const customerId = String ( rate ? . tenantId || filters . value . customerId || '' )
if ( ! customerId ) {
ElMessage . warning ( '新增价格版本需要先选择所属企业' )
return
}
if ( loadedCustomerId . value !== customerId ) {
const loaded = await loadCustomerMasterData ( customerId )
if ( ! loaded ) return
}
editorCustomerId . value = customerId
resetForm ( rate )
captureBaseline ( )
editorOpen . value = true
requestAnimationFrame ( ( ) => formRef . value ? . clearValidate ( ) )
}
async function openDetail ( row : PlatformCarrierRateResource ) {
detailOpen . value = true
detailLoading . value = true
detail . value = null
try {
detail . value = await getPlatformCarrierRate ( String ( row . tenantId ) , row . id )
} catch ( error ) {
const meta = getApiErrorDetails ( error , '价格版本详情加载失败' )
ElMessage . error ( meta . requestId ? ` ${ meta . message } ( requestId: ${ meta . requestId } ) ` : meta . message )
detailOpen . value = false
} finally {
detailLoading . value = false
}
}
function selectedOriginWarehouse ( ) {
return originWarehouses . value . find ( ( item ) => String ( item . id ) === form . originWarehouseId )
}
function selectedCarrier ( ) {
return activeCarriers . value . find ( ( item ) => String ( item . id ) === form . carrierId )
}
function buildPayload ( ) : CreatePlatformCarrierRateVersionRequest | null {
const warehouse = selectedOriginWarehouse ( )
const carrier = selectedCarrier ( )
if ( ! warehouse || ! carrier || form . unitPrice == null || form . additionalFee == null ) return null
return {
rateCode : form . rateCode . trim ( ) ,
originWarehouseId : String ( warehouse . id ) ,
originWarehouseName : warehouse . warehouseName ,
businessType : form . businessType . trim ( ) ,
channelCode : form . channelCode . trim ( ) ,
carrierId : String ( carrier . id ) ,
carrierName : carrier . partnerName ,
destinationCode : form . destinationCode . trim ( ) . toUpperCase ( ) ,
chargeUnit : form . chargeUnit ,
unitPrice : form . unitPrice ,
currency : form . currency . trim ( ) . toUpperCase ( ) ,
additionalFee : form . additionalFee ,
effectiveFrom : form . effectiveFrom ,
effectiveTo : nullable ( form . effectiveTo ) ,
remark : nullable ( form . remark ) ,
}
}
async function saveVersion ( ) {
const customerId = editorCustomerId . value
if ( ! customerId ) return
const valid = await formRef . value ? . validate ( ) . catch ( ( ) => false )
if ( ! valid ) return
const payload = buildPayload ( )
if ( ! payload ) {
ElMessage . error ( '起运仓或船司主数据已变化,请重新选择' )
return
}
saving . value = true
try {
await createPlatformCarrierRateVersion ( customerId , payload )
editorOpen . value = false
ElMessage . success ( '船司 FBA 价格新版本已创建' )
await loadRateData ( filters . value . customerId )
} catch ( error ) {
const meta = getApiErrorDetails ( error , '价格版本创建失败,请检查有效期和匹配维度后重试' )
ElMessage . error ( meta . requestId ? ` ${ meta . message } ( requestId: ${ meta . requestId } ) ` : meta . message )
} finally {
saving . value = false
}
}
async function retireRate ( row : PlatformCarrierRateResource ) {
try {
await ElMessageBox . confirm (
` 停用“ ${ row . rateCode } V ${ row . rateVersion } ”后,新费用试算不会再匹配该版本,历史费用快照不受影响。 ` ,
'停用价格版本' ,
{ type : 'warning' , confirmButtonText : '确认停用' , cancelButtonText : '取消' } ,
)
retiringIds . value = new Set ( retiringIds . value ) . add ( row . id )
await retirePlatformCarrierRate ( String ( row . tenantId ) , row . id )
ElMessage . success ( '价格版本已停用' )
await loadRateData ( filters . value . customerId )
} catch ( error ) {
if ( error === 'cancel' || error === 'close' ) return
const meta = getApiErrorDetails ( error , '价格版本停用失败,请重试' )
ElMessage . error ( meta . requestId ? ` ${ meta . message } ( requestId: ${ meta . requestId } ) ` : meta . message )
} finally {
const next = new Set ( retiringIds . value )
next . delete ( row . id )
retiringIds . value = next
}
}
function closeEditor ( ) {
if ( saving . value ) return
editorOpen . value = false
}
onMounted ( ( ) => {
void loadCustomers ( )
void loadRateData ( )
} )
< / script >
< template >
< div class = "business-page" >
< QueryToolbar
v-model = "filters"
ariaLabel = "船司 FBA 价格筛选"
:fields = "queryFields"
@search ="applyQuery"
@reset ="resetQuery"
/ >
< el-alert v-if = "customerLoadError" type="warning" :closable="false" show-icon role="alert" >
< template # title > < div class = "api-error-alert-title" > < span > { { customerLoadError . message } } < / span > < ApiErrorMeta :details = "customerLoadError" / > < / div > < / template >
< el-button link type = "warning" @click ="loadCustomers" > 重新加载企业列表 < / el -button >
< / el-alert >
< LoadErrorState
v-if = "loadError"
title = "船司 FBA 价格加载失败"
:message = "loadErrorMessage"
:loading = "loading"
@retry ="loadRateData()"
>
< template # details > < ApiErrorMeta :details = "loadError" / > < / template >
< / LoadErrorState >
< section class = "surface-panel table-panel" aria -live = " polite " >
< div class = "panel-heading" >
< div >
< h2 > 价格版本 < / h2 >
< p > 共 { { rows . length } } 个版本 · { { activeCount } } 个生效中 < / p >
< / div >
< el-button
v-if = "access.can('firstleg:fba-pricing:rate:edit')"
type = "primary"
: disabled = "!filters.customerId || masterDataLoading"
@click ="openCreate()"
> < Plus :size = "16" / > 新增价格版本 < / el-button >
< / div >
< DataTable
v -model :page = "page"
v -model :page-size = "pageSize"
:data = "pagedRows"
:columns = "columns"
:loading = "loading"
:total = "filteredRows.length"
: page -sizes = " [ 10 , 20 , 50 ] "
row -key = " id "
aria -label = " 船司 FBA 价格版本列表 "
empty -text = " 暂无符合条件的价格版本 "
>
< template # actions = "{ row }" >
< div class = "table-actions" >
< el-button link type = "primary" @click ="openDetail(row as PlatformCarrierRateResource)" > < Eye :size = "15" / > 详情 < / el-button >
< el-button
v-if = "access.can('firstleg:fba-pricing:rate:edit')"
link
type = "primary"
@click ="openCreate(row as PlatformCarrierRateResource)"
> < Plus :size = "15" / > 新版本 < / el-button >
< el-button
v-if = "access.can('firstleg:fba-pricing:rate:edit') && (row as PlatformCarrierRateResource).status === 'ACTIVE'"
link
type = "danger"
: loading = "retiringIds.has((row as PlatformCarrierRateResource).id)"
@click ="retireRate(row as PlatformCarrierRateResource)"
> < Power :size = "15" / > 停用 < / el-button >
< / div >
< / template >
< template # customer = "{ row }" > < strong > { { customerLabel ( String ( ( row as PlatformCarrierRateResource ) . tenantId ) ) } } < / strong > < / template >
< template # rate = "{ row }" >
< div class = "primary-cell" > < strong > { { ( row as PlatformCarrierRateResource ) . rateCode } } < / strong > < span > 版本 V { { ( row as PlatformCarrierRateResource ) . rateVersion } } < / span > < / div >
< / template >
< template # carrier = "{ row }" > < div class = "primary-cell" > < strong > { { ( row as PlatformCarrierRateResource ) . carrierName } } < / strong > < span > ID { { ( row as PlatformCarrierRateResource ) . carrierId } } < / span > < / div > < / template >
< template # unitPrice = "{ row }" > < strong > { { formatMoney ( ( row as PlatformCarrierRateResource ) . unitPrice , ( row as PlatformCarrierRateResource ) . currency ) } } < / strong > < / template >
< template # additionalFee = "{ row }" > { { formatMoney ( ( row as PlatformCarrierRateResource ) . additionalFee , ( row as PlatformCarrierRateResource ) . currency ) } } < / template >
< template # period = "{ row }" > < div class = "primary-cell" > < strong > { { formatDateTime ( ( row as PlatformCarrierRateResource ) . effectiveFrom ) } } < / strong > < span > 至 { { formatDateTime ( ( row as PlatformCarrierRateResource ) . effectiveTo ) } } < / span > < / div > < / template >
< template # status = "{ row }" > < StatusBadge : status = "statusLabel((row as PlatformCarrierRateResource).status)" / > < / template >
< template # empty > < el-empty description = "暂无符合条件的价格版本" / > < / template >
< / DataTable >
< / section >
< AppDrawer
v-model = "editorOpen"
:title = "editorLabel"
size = "min(760px, 96vw)"
:confirm-loading = "saving"
confirm -text = " 创建版本 "
@confirm ="saveVersion"
@cancel ="closeEditor"
>
< el-alert type = "info" :closable = "false" show -icon class = "version-note" >
价格版本创建后不可原地修改 。 相同匹配维度的生效时间不得重叠 ; 需要变价时 , 请创建新版本并停用旧版本 。
< / el-alert >
< el-form ref = "formRef" :model = "form" :rules = "rules" label -position = " top " class = "drawer-form" :disabled = "saving" >
< section class = "form-section" >
< h3 > 版本与运输匹配 < / h3 >
< div class = "form-grid two-cols" >
< el-form-item label = "价格编码" prop = "rateCode" > < el-input v-model = "form.rateCode" maxlength="50" placeholder="例如 FBA-US-WEST" / > < / el-form-item >
< el-form-item label = "起运仓" prop = "originWarehouseId" >
< el-select v-model = "form.originWarehouseId" filterable placeholder="请选择启用的国内收货仓" :loading="masterDataLoading" >
< el -option v-for = "warehouse in originWarehouses" :key="String(warehouse.id)" :label="`${warehouse.warehouseName}( ${warehouse.warehouseCode}) `" :value="String(warehouse.id)" / >
< / el-select >
< / el-form-item >
< el-form-item label = "业务类型" prop = "businessType" > < el-input v-model = "form.businessType" maxlength="32" placeholder="请输入后端使用的业务类型" / > < / el-form-item >
< el-form-item label = "渠道编码" prop = "channelCode" > < el-input v-model = "form.channelCode" maxlength="64" placeholder="请输入渠道编码" / > < / el-form-item >
< el-form-item label = "船司" prop = "carrierId" >
< el-select v-model = "form.carrierId" filterable placeholder="请选择启用的承运商" :loading="masterDataLoading" >
< el -option v-for = "carrier in activeCarriers" :key="String(carrier.id)" :label="`${carrier.partnerName}( ${carrier.partnerCode}) `" :value="String(carrier.id)" / >
< / el-select >
< / el-form-item >
< el-form-item label = "FBA 目的仓" prop = "destinationCode" >
< el-select v-model = "form.destinationCode" filterable allow-create default-first-option placeholder="选择或输入 FBA 仓编码" >
< el -option v-for = "warehouse in fbaWarehouses" :key="String(warehouse.id)" :label="`${warehouse.warehouseName}( ${warehouse.warehouseCode}) `" :value="warehouse.warehouseCode" / >
< / el-select >
< / el-form-item >
< / div >
< / section >
< section class = "form-section" >
< h3 > 计费口径 < / h3 >
< div class = "form-grid two-cols" >
< el-form-item label = "计费单位" prop = "chargeUnit" > < el-segmented v-model = "form.chargeUnit" :options="['KG', 'CBM']" / > < / el-form-item >
< el-form-item label = "单价" prop = "unitPrice" > < el-input-number v-model = "form.unitPrice" :min="0" :precision="4" :step="0.01" controls-position="right" / > < / el-form-item >
< el-form-item label = "币种" prop = "currency" > < el-input v-model = "form.currency" maxlength="3" placeholder="CNY" @input="form.currency = String($event).toUpperCase()" / > < / el-form-item >
< el-form-item label = "固定附加费" prop = "additionalFee" > < el-input-number v-model = "form.additionalFee" :min="0" :precision="2" :step="1" controls-position="right" / > < / el-form-item >
< / div >
< / section >
< section class = "form-section" >
< h3 > 有效期 < / h3 >
< div class = "form-grid two-cols" >
< el-form-item label = "生效时间" prop = "effectiveFrom" > < el-date-picker v-model = "form.effectiveFrom" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="选择生效时间" / > < / el-form-item >
< el-form-item label = "结束时间" prop = "effectiveTo" > < el-date-picker v-model = "form.effectiveTo" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="留空表示长期有效" / > < / el-form-item >
< / div >
< el-form-item label = "备注" > < el-input v-model = "form.remark" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="记录版本适用说明或变更原因" / > < / el-form-item >
< / section >
< / el-form >
< / AppDrawer >
< AppDrawer v-model = "detailOpen" title="船司 FBA 价格详情" size="min(680px, 96vw)" :loading="detailLoading" :show-footer="false" >
< div v-if = "detail" class="detail-content" >
< header class = "detail-heading" >
< div > < strong > { { detail . rateCode } } < / strong > < span > 版本 V { { detail . rateVersion } } < / span > < / div >
< StatusBadge :status = "statusLabel(detail.status)" / >
< / header >
< section class = "detail-section" > < h3 > < Warehouse :size = "17" / > 运输匹配 < / h3 > < dl class = "detail-grid" > < div > < dt > 所属企业 < / dt > < dd > { { customerLabel ( detail . tenantId ) } } < / dd > < / div > < div > < dt > 起运仓 < / dt > < dd > { { detail . originWarehouseName } } < / dd > < / div > < div > < dt > FBA 目的仓 < / dt > < dd > { { detail . destinationCode } } < / dd > < / div > < div > < dt > 业务类型 < / dt > < dd > { { detail . businessType } } < / dd > < / div > < div > < dt > 渠道编码 < / dt > < dd > { { detail . channelCode } } < / dd > < / div > < div > < dt > 船司 < / dt > < dd > { { detail . carrierName } } < / dd > < / div > < div > < dt > 船司 ID < / dt > < dd > { { detail . carrierId } } < / dd > < / div > < / dl > < / section >
< section class = "detail-section" > < h3 > < Ship :size = "17" / > 计费口径 < / h3 > < dl class = "detail-grid" > < div > < dt > 计费单位 < / dt > < dd > { { detail . chargeUnit } } < / dd > < / div > < div > < dt > 币种 < / dt > < dd > { { detail . currency } } < / dd > < / div > < div > < dt > 单价 < / dt > < dd > { { formatMoney ( detail . unitPrice , detail . currency ) } } < / dd > < / div > < div > < dt > 固定附加费 < / dt > < dd > { { formatMoney ( detail . additionalFee , detail . currency ) } } < / dd > < / div > < / dl > < / section >
< section class = "detail-section" > < h3 > < CalendarClock :size = "17" / > 版本有效期 < / h3 > < dl class = "detail-grid" > < div > < dt > 生效时间 < / dt > < dd > { { formatDateTime ( detail . effectiveFrom ) } } < / dd > < / div > < div > < dt > 结束时间 < / dt > < dd > { { formatDateTime ( detail . effectiveTo ) } } < / dd > < / div > < div > < dt > 更新时间 < / dt > < dd > { { formatDateTime ( detail . updatedAt ) } } < / dd > < / div > < div > < dt > 记录版本 < / dt > < dd > { { detail . version ? ? '-' } } < / dd > < / div > < / dl > < p v-if = "detail.remark" class="detail-remark" > {{ detail.remark }} < / p > < / section >
< / div >
< / AppDrawer >
< / div >
< / template >
< style scoped >
. version - note { margin - bottom : 18 px ; }
. form - grid : deep ( . el - select ) , . form - grid : deep ( . el - date - editor ) , . form - grid : deep ( . el - input - number ) , . form - grid : deep ( . el - segmented ) { width : 100 % ; }
. detail - content { display : grid ; gap : 18 px ; }
. detail - heading { display : flex ; align - items : flex - start ; justify - content : space - between ; gap : 16 px ; padding - bottom : 16 px ; border - bottom : 1 px solid var ( -- el - border - color - lighter ) ; }
. detail - heading > div { display : flex ; min - width : 0 ; flex - direction : column ; gap : 4 px ; }
. detail - heading strong { font - size : 18 px ; overflow - wrap : anywhere ; }
. detail - heading span { color : var ( -- cc - muted ) ; font - size : 12 px ; }
. detail - section { padding : 2 px 0 8 px ; }
. detail - section h3 { display : flex ; align - items : center ; gap : 8 px ; margin : 0 0 14 px ; font - size : 14 px ; }
. detail - section h3 svg { color : var ( -- cc - primary ) ; }
. detail - grid { display : grid ; grid - template - columns : repeat ( 2 , minmax ( 0 , 1 fr ) ) ; gap : 14 px 24 px ; margin : 0 ; }
. detail - grid div { min - width : 0 ; }
. detail - grid dt { margin - bottom : 5 px ; color : var ( -- cc - muted ) ; font - size : 12 px ; }
. detail - grid dd { margin : 0 ; font - size : 13 px ; line - height : 1.55 ; overflow - wrap : anywhere ; }
. detail - remark { margin : 16 px 0 0 ; padding : 12 px 14 px ; border - radius : 10 px ; background : var ( -- el - fill - color - light ) ; color : var ( -- el - text - color - regular ) ; font - size : 13 px ; line - height : 1.65 ; white - space : pre - wrap ; }
@ media ( max - width : 720 px ) {
. panel - heading . el - button { width : 100 % ; min - height : 44 px ; }
. detail - grid { grid - template - columns : 1 fr ; gap : 12 px ; }
}
< / style >