@@ -736,6 +736,25 @@ function pricingTaxModeLabel(mode: 'TAX_INCLUDED' | 'TAX_EXCLUDED') {
return mode === 'TAX_INCLUDED' ? '包税' : '不包税'
}
function formatEstimateMoney ( value : number , currency : string ) {
try {
return new Intl . NumberFormat ( 'zh-CN' , {
style : 'currency' ,
currency ,
minimumFractionDigits : 2 ,
maximumFractionDigits : 2 ,
} ) . format ( value )
} catch {
return ` ${ currency } ${ value . toFixed ( 2 ) } `
}
}
function formatEstimateQuantity ( value : number , unit : string ) {
return ` ${ value . toLocaleString ( 'zh-CN' , { maximumFractionDigits : 4 } )} ${ unit } `
}
const priceEstimateDisclaimer = computed ( ( ) => priceEstimate . value ? . disclaimer || '仅供参考,最终以实际结算为准' )
function resetPriceEstimate ( ) {
estimateRequestSequence += 1
priceEstimate . value = null
@@ -815,6 +834,10 @@ async function loadPriceEstimate() {
}
function schedulePriceEstimate ( ) {
estimateRequestSequence += 1
priceEstimate . value = null
priceEstimateError . value = ''
priceEstimateLoading . value = false
if ( estimateTimer ) clearTimeout ( estimateTimer )
estimateTimer = setTimeout ( ( ) => void loadPriceEstimate ( ) , 300 )
}
@@ -1389,6 +1412,17 @@ async function importCargoTemplate(event: Event) {
}
async function validateEditorForm ( ) {
try {
const policy = await getMerchantInboundCodePolicy ( )
inboundCodePolicy . value = policy
if ( policy . mode === 'AUTO' && ! editingId . value ) form . inboundCode = ''
await nextTick ( )
} catch ( error ) {
inboundCodePolicy . value = null
ElMessage . warning ( ` 入仓码配置加载失败: ${ errorMessage ( error ) } ` )
return false
}
let fieldsValid = false
try {
fieldsValid = await orderFormRef . value ? . validate ( ) ? ? false
@@ -1400,10 +1434,6 @@ async function validateEditorForm() {
return false
}
if ( ! inboundCodePolicy . value ) {
ElMessage . warning ( '入仓码配置尚未加载,请稍后重试' )
return false
}
if ( manualInboundCodeRequired . value && ! form . inboundCode ? . trim ( ) ) {
ElMessage . warning ( '当前为旧系统并跑模式,请填写旧系统入仓码' )
return false
@@ -1730,28 +1760,28 @@ async function searchOrders(model: QueryModel) {
< / section >
< section class = "order-form-section prototype-section cost-estimate-section" aria -label = " 费用预估 " >
< div class = "order-section-heading" > < div > < h3 > 费用预估 < / h3 > < / div > < / div >
< div class = "cost -estimate-cards " >
< article class = "cost-estimate-card" >
< span class = "cost-estimate-badge is-fastest" > 最快 < / span >
< strong class = "cost-estimate-days" > 10 天 < / strong >
< div class = "cost-estimate-price" > < small > ¥ < / small > < strong > 3 , 310 < / strong > < / div >
< p > 优先配送 , 时效保障 < / p >
< / article >
< article class = "cost-e stimate-card is-recommended " >
< span class = "cost -estimate-badge is-recommend" > 推荐 < / span >
< strong class = "cost-estimate-days" > 13 天 < / strong >
< div class = "cost-estimate-price" > < small > ¥ < / small > < strong > 2 , 990 < / strong > < / div >
< p > 性价比之选 < / p >
< / article >
< article class = "cost-estimate-card" >
< span class = "cost-estimate-badge is-economy" > 经济 < / span >
< strong class = "cost -estimate-days" > 16 天 < / strong >
< div class = "cost-estimate-price" > < small > ¥ < / small > < strong > 2 , 500 < / strong > < / div >
< p > 经济实惠 < / p >
< / article >
< div class = "order-section-heading" > < div > < h3 > 参考运 费< / h3 > < p > 根据当前价格表 、 渠道 、 目的仓以及货物重量和体积实时试算 。 < / p > < / div > < / div >
< div class = "price -estimate" : class = "{ 'is-loading': priceEstimateLoading }" aria -live = " polite " >
< div v-if = "priceEstimateLoading" class="price-estimate__state" v-loading="true" > 正在获取实时参考报价 < / div >
< div v-else-if = "priceEstimateError" class="price-estimate__state is-error" role="alert" >
< AlertTriangle :size = "20" / >
< div > < strong > 参考报价暂时不可用 < / strong > < span > { { priceEstimateError } } , 您仍可正常保存订单草稿 。 < / span > < / div >
< el-button @click ="loadPriceEstimate" > 重新试算 < / el -button >
< / div >
< div v-else-if = "priceE stimate" class="price-estimate__result " >
< div class = "price -estimate__channel" >
< span > 当前渠道 < / span >
< strong > { { selectedPricingChannel ? . channelName || priceEstimate . channelCode } } < / strong >
< small > { { pricingTaxModeLabel ( priceEstimate . pricingTaxMode ) } } < template v-if = "selectedPricingChannel?.referenceTransit" > · 参考时效 {{ selectedPricingChannel.referenceTransit }} < / template > < / small >
< / div >
< div > < span > 计费量 < / span > < strong > { { formatEstimateQuantity ( priceEstimate . chargeableQuantity , priceEstimate . chargeUnit ) } } < / strong > < / div >
< div > < span > 基础单价 < / span > < strong > { { formatEstimateMoney ( priceEstimate . unitPrice , priceEstimate . currency ) } } / { { priceEstimate . chargeUnit } } < / strong > < / div >
< div class = "price -estimate__amount" > < span > 参考运费 < / span > < strong > { { formatEstimateMoney ( priceEstimate . estimatedAmount , priceEstimate . currency ) } } < / strong > < / div >
< small > { { priceEstimateDisclaimer } } < / small >
< / div >
< div v-else-if = "form.destinationType === 'PRIVATE'" class="price-estimate__state" > 私人仓暂不提供在线参考报价 , 您仍可正常保存订单草稿 。 < / div >
< div v-else class = "price-estimate__state" > 请先选择国内收货仓 、 渠道 、 目的仓 , 并填写大于 0 的总毛重和总体积 。 < / div >
< / div >
< p class = "cost-estimate-note" > 以上费用为静态预估展示 , 实际费用以最终结算结果为准 。 < / p >
< / section >
< / el-form >
< template # footer > < div class = "editor-actions" > < el-button :disabled = "saving" @click ="closeEditor" > 返回 < / el -button > < el-button v-if = "editingId ? canEditOrder : canCreateOrder" :loading="saving" @click="saveDraft">保存草稿</el-button><el-button v-if="auth.hasPermission('order:first-leg:submit')" type="primary" :loading="saving" @click="submitOrder"><Send :size="16" / > { { companySubmissionBlocked ? '提交前需完成资料审核' : '提交订单' } } < / el-button > < / div > < / template >
@@ -2015,18 +2045,20 @@ async function searchOrders(model: QueryModel) {
. field - guidance -- error { color : # b54708 ; }
. cost - estimate - section { width : 100 % ; }
. cost - estimate - section . order - section - heading { margin - bottom : 12 px ; }
. cost - estimate - cards { display : grid ; grid - template - columns : repeat ( 3 , minmax ( 0 , 1 fr ) ) ; gap : 14 px ; }
. cost - estimate - card { position : relative ; display : flex ; min - height : 158 px ; box - sizing : border - box ; align - items : center ; justify - content : center ; flex - direction : column ; padding : 24 px 18 px 18 px ; border : 1 px solid # dde4f2 ; border - radius : 8 px ; background : # fff ; box - shadow : 0 2 px 8 px rgba ( 15 , 31 , 58 , .025 ) ; text - align : center ; }
. cost - estimate - card . is - recommended { border : 2 px solid # 637 fec ; background : # f8f8ff ; box - shadow : 0 8 px 22 px rgba ( 99 , 127 , 236 , .13 ) ; }
. cost - estimate - badge { position : absolute ; top : - 9 px ; right : 14 px ; padding : 3 px 11 px ; border - radius : 12 px ; color : # fff ; font - size : 10 px ; font - weight : 650 ; line - height : 1.4 ; }
. cost - estimate - badge . is - fastest , . cost - estimate - badge . is - recommend { background : # 5368 c6 ; }
. cost - estimate - badge . is - economy { background : # 71858 e ; }
. cost - estimate - days { color : # 30343 b ; font - size : 21 px ; }
. cost - estimate - price { display : flex ; align - items : baseline ; gap : 5 px ; margin - top : 13 px ; color : # 637 fec ; }
. cost - estimate - price small { font - size : 15 px ; font - weight : 700 ; }
. cost - estimate - price strong { font - size : 29 px ; line - height : 1 ; }
. cost - estimate - card p { margin : 12 px 0 0 ; color : # 98 a0ad ; font - size : 11 px ; }
. cost - estimate - note { margin : 10 px 0 0 ; color : # 98 a0ad ; font - size : 10 px ; text - align : right ; }
. price - estimate { min - height : 126 px ; overflow : hidden ; border : 1 px solid # dde4f2 ; border - radius : 8 px ; background : # fff ; box - shadow : 0 2 px 8 px rgba ( 15 , 31 , 58 , .025 ) ; }
. price - estimate _ _state { display : flex ; min - height : 126 px ; align - items : center ; justify - content : center ; gap : 10 px ; padding : 24 px ; color : # 7 b8798 ; font - size : 12 px ; text - align : center ; }
. price - estimate _ _state . is - error { justify - content : flex - start ; background : # fffaf0 ; color : # b54708 ; text - align : left ; }
. price - estimate _ _state . is - error > div { display : flex ; min - width : 0 ; flex : 1 ; flex - direction : column ; gap : 4 px ; }
. price - estimate _ _state . is - error strong { color : # 7 a2e0e ; font - size : 13 px ; }
. price - estimate _ _state . is - error span { overflow - wrap : anywhere ; }
. price - estimate _ _result { display : grid ; grid - template - columns : minmax ( 190 px , 1.3 fr ) repeat ( 2 , minmax ( 140 px , .8 fr ) ) minmax ( 190 px , 1 fr ) ; align - items : stretch ; }
. price - estimate _ _result > div { display : flex ; min - width : 0 ; min - height : 96 px ; justify - content : center ; flex - direction : column ; gap : 7 px ; padding : 18 px 20 px ; border - right : 1 px solid # edf0f5 ; }
. price - estimate _ _result > div > span { color : # 8 b96a6 ; font - size : 11 px ; }
. price - estimate _ _result > div > strong { overflow - wrap : anywhere ; color : # 303 b50 ; font - size : 15 px ; }
. price - estimate _ _channel small { color : # 7 b8798 ; font - size : 10 px ; line - height : 1.5 ; }
. price - estimate _ _result . price - estimate _ _amount { border - right : 0 ; background : # f8f8ff ; }
. price - estimate _ _result . price - estimate _ _amount strong { color : # 637 fec ; font - size : 25 px ; }
. price - estimate _ _result > small { grid - column : 1 / - 1 ; padding : 9 px 14 px ; border - top : 1 px solid # edf0f5 ; color : # 98 a0ad ; font - size : 10 px ; text - align : right ; }
. selected - destination { display : flex ; width : min ( 100 % , 720 px ) ; align - items : flex - start ; gap : 9 px ; margin : - 4 px 0 18 px 140 px ; padding : 10 px 12 px ; background : # f2f7f6 ; color : var ( -- primary ) ; }
. selected - destination svg { flex : 0 0 17 px ; margin - top : 2 px ; }
. selected - destination div { display : flex ; min - width : 0 ; flex - direction : column ; gap : 2 px ; }
@@ -2224,8 +2256,11 @@ async function searchOrders(model: QueryModel) {
. compact - transport - grid > : deep ( . el - form - item _ _label ) { min - height : auto ; justify - content : flex - start ; padding : 0 0 8 px ; line - height : 1.4 ; }
. cargo - table - toolbar { align - items : stretch ; flex - direction : column ; }
. cargo - import - actions , . cargo - table - actions { flex - wrap : wrap ; }
. cost - estimate - cards { grid - template - columns : 1 fr ; }
. cost - estimate - card { min - height : 140 px ; }
. price - estimate _ _result { grid - template - columns : 1 fr ; }
. price - estimate _ _result > div { min - height : auto ; border - right : 0 ; border - bottom : 1 px solid # edf0f5 ; }
. price - estimate _ _result > small { grid - column : auto ; }
. price - estimate _ _result > small { text - align : left ; }
. price - estimate _ _state . is - error { align - items : flex - start ; flex - direction : column ; }
. form - span - full { grid - column : auto ; }
. editor - actions : deep ( . el - button ) { min - height : 44 px ; }
. desktop - order - table { display : none ; }