2093 lines
61 KiB
Vue
2093 lines
61 KiB
Vue
<template>
|
||
<a-drawer
|
||
:visible="visible"
|
||
placement="bottom"
|
||
height="auto"
|
||
:footer="false"
|
||
:header="false"
|
||
unmount-on-close
|
||
:mask-closable="!submitting"
|
||
:drawer-style="drawerStyle"
|
||
popup-container="body"
|
||
class="h5-po-drawer"
|
||
@cancel="close"
|
||
>
|
||
<div class="h5-po">
|
||
<div class="h5-po__handle" aria-hidden="true" />
|
||
|
||
<header class="h5-po__head">
|
||
<h3>{{ t('h5.placeOrder.title') }}</h3>
|
||
<button type="button" class="h5-po__close" :disabled="submitting" @click="close">
|
||
<icon-close />
|
||
</button>
|
||
</header>
|
||
|
||
<div class="h5-po__steps">
|
||
<button
|
||
v-for="(s, idx) in steps"
|
||
:key="s.key"
|
||
type="button"
|
||
class="h5-po__step"
|
||
:class="{ 'is-active': step === idx, 'is-done': idx < step }"
|
||
@click="goToStep(idx)"
|
||
>
|
||
{{ idx + 1 }}.{{ s.label }}
|
||
</button>
|
||
</div>
|
||
|
||
<p v-if="handoffTip" class="h5-po__tip">
|
||
<span>{{ handoffTip }}</span>
|
||
<button type="button" @click="handoffTip = ''">×</button>
|
||
</p>
|
||
|
||
<a-spin :loading="optionsLoading" class="h5-po__spin">
|
||
<section v-show="step === 0" class="h5-po__card">
|
||
<div class="h5-po__sender-head">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.sender') }}</label>
|
||
<button type="button" class="h5-po__link" @click="showSenderForm = !showSenderForm">
|
||
{{ showSenderForm ? t('h5.placeOrder.cancelAddSender') : t('h5.placeOrder.addSender') }}
|
||
</button>
|
||
</div>
|
||
|
||
<div v-if="showSenderForm" class="h5-po__sender-form">
|
||
<div class="h5-po__row">
|
||
<a-input v-model="senderForm.senderName" :placeholder="t('h5.placeOrder.senderNamePh')" />
|
||
<a-input v-model="senderForm.phone" :placeholder="t('h5.placeOrder.senderPhonePh')" />
|
||
</div>
|
||
<div class="h5-po__row">
|
||
<a-input v-model="senderForm.province" :placeholder="t('h5.placeOrder.senderProvincePh')" />
|
||
<a-input v-model="senderForm.city" :placeholder="t('h5.placeOrder.senderCityPh')" />
|
||
</div>
|
||
<a-textarea
|
||
v-model="senderForm.streetAddress1"
|
||
:placeholder="t('h5.placeOrder.senderAddressPh')"
|
||
:auto-size="{ minRows: 2, maxRows: 3 }"
|
||
/>
|
||
<button
|
||
type="button"
|
||
class="h5-po__btn is-primary h5-po__btn--block"
|
||
:disabled="savingSender"
|
||
@click="saveSender"
|
||
>
|
||
{{ savingSender ? t('h5.placeOrder.savingSender') : t('h5.placeOrder.saveSender') }}
|
||
</button>
|
||
</div>
|
||
|
||
<template v-else>
|
||
<p v-if="!senderLoading && !senderOptions.length" class="h5-po__sender-empty">
|
||
{{ t('h5.placeOrder.senderEmpty') }}
|
||
</p>
|
||
<a-select
|
||
:model-value="selectedSenderId"
|
||
allow-clear
|
||
:loading="senderLoading"
|
||
:error="fieldHasError('sender')"
|
||
:placeholder="t('h5.placeOrder.senderPh')"
|
||
@change="onSenderChange"
|
||
>
|
||
<a-option v-for="opt in senderOptions" :key="opt.id" :value="opt.id">
|
||
{{ opt.label }}
|
||
</a-option>
|
||
</a-select>
|
||
<p v-if="fieldError('sender')" class="h5-po__field-err">{{ fieldError('sender') }}</p>
|
||
</template>
|
||
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.clientNo') }}</label>
|
||
<a-input
|
||
v-model="form.clientNo"
|
||
allow-clear
|
||
:error="fieldHasError('clientNo')"
|
||
:placeholder="t('h5.placeOrder.clientNoPh')"
|
||
/>
|
||
<p v-if="fieldError('clientNo')" class="h5-po__field-err">{{ fieldError('clientNo') }}</p>
|
||
</div>
|
||
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.product') }}</label>
|
||
<a-select
|
||
v-model="form.productCode"
|
||
allow-search
|
||
allow-clear
|
||
:error="fieldHasError('productCode')"
|
||
:placeholder="t('h5.placeOrder.productPh')"
|
||
>
|
||
<a-option v-for="opt in productOptions" :key="opt.value" :value="opt.value">
|
||
{{ opt.label }}
|
||
</a-option>
|
||
</a-select>
|
||
<p v-if="fieldError('productCode')" class="h5-po__field-err">{{ fieldError('productCode') }}</p>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.cargo') }}</label>
|
||
<a-select v-model="form.cargoAttribute" allow-clear :error="fieldHasError('cargoAttribute')">
|
||
<a-option v-for="opt in cargoOptions" :key="opt.value" :value="opt.value">
|
||
{{ opt.label }}
|
||
</a-option>
|
||
</a-select>
|
||
<p v-if="fieldError('cargoAttribute')" class="h5-po__field-err">
|
||
{{ fieldError('cargoAttribute') }}
|
||
</p>
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.warehouse') }}</label>
|
||
<a-select v-model="form.warehouseId" allow-clear :error="fieldHasError('warehouseId')">
|
||
<a-option v-for="opt in warehouseOptions" :key="opt.value" :value="opt.value">
|
||
{{ opt.label }}
|
||
</a-option>
|
||
</a-select>
|
||
<p v-if="fieldError('warehouseId')" class="h5-po__field-err">
|
||
{{ fieldError('warehouseId') }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">
|
||
{{ t('h5.placeOrder.weight') }} / {{ t('h5.placeOrder.dimensions') }}
|
||
</label>
|
||
<div class="h5-po__dims h5-po__dims--with-weight">
|
||
<a-input
|
||
v-model="form.actualWeight"
|
||
allow-clear
|
||
:error="fieldHasError('actualWeight')"
|
||
:placeholder="t('h5.placeOrder.weightPh')"
|
||
/>
|
||
<a-input
|
||
v-model="form.length"
|
||
:error="fieldHasError('length')"
|
||
:placeholder="t('h5.placeOrder.length')"
|
||
/>
|
||
<a-input
|
||
v-model="form.width"
|
||
:error="fieldHasError('width')"
|
||
:placeholder="t('h5.placeOrder.width')"
|
||
/>
|
||
<a-input
|
||
v-model="form.height"
|
||
:error="fieldHasError('height')"
|
||
:placeholder="t('h5.placeOrder.height')"
|
||
/>
|
||
</div>
|
||
<p
|
||
v-if="
|
||
fieldError('actualWeight') ||
|
||
fieldError('length') ||
|
||
fieldError('width') ||
|
||
fieldError('height')
|
||
"
|
||
class="h5-po__field-err"
|
||
>
|
||
{{
|
||
fieldError('actualWeight') ||
|
||
fieldError('length') ||
|
||
fieldError('width') ||
|
||
fieldError('height')
|
||
}}
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
<section v-show="step === 1" class="h5-po__card">
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.receiverName') }}</label>
|
||
<a-input
|
||
v-model="form.receiver.recipientsName"
|
||
allow-clear
|
||
:error="fieldHasError('receiver.recipientsName')"
|
||
/>
|
||
<p v-if="fieldError('receiver.recipientsName')" class="h5-po__field-err">
|
||
{{ fieldError('receiver.recipientsName') }}
|
||
</p>
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.receiverPhone') }}</label>
|
||
<a-input
|
||
v-model="form.receiver.phone"
|
||
allow-clear
|
||
:error="fieldHasError('receiver.phone')"
|
||
/>
|
||
<p v-if="fieldError('receiver.phone')" class="h5-po__field-err">
|
||
{{ fieldError('receiver.phone') }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.country') }}</label>
|
||
<a-select
|
||
v-model="form.receiver.countryCode"
|
||
allow-search
|
||
allow-clear
|
||
:error="fieldHasError('receiver.countryCode')"
|
||
>
|
||
<a-option v-for="opt in countryOptions" :key="opt.value" :value="opt.value">
|
||
{{ opt.label }}
|
||
</a-option>
|
||
</a-select>
|
||
<p v-if="fieldError('receiver.countryCode')" class="h5-po__field-err">
|
||
{{ fieldError('receiver.countryCode') }}
|
||
</p>
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.zip') }}</label>
|
||
<a-input
|
||
v-model="form.receiver.zipCode"
|
||
allow-clear
|
||
:error="fieldHasError('receiver.zipCode')"
|
||
/>
|
||
<p v-if="fieldError('receiver.zipCode')" class="h5-po__field-err">
|
||
{{ fieldError('receiver.zipCode') }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.address') }}</label>
|
||
<a-textarea
|
||
v-model="form.receiver.streetAddress1"
|
||
:error="fieldHasError('receiver.streetAddress1')"
|
||
:auto-size="{ minRows: 2, maxRows: 3 }"
|
||
/>
|
||
<p v-if="fieldError('receiver.streetAddress1')" class="h5-po__field-err">
|
||
{{ fieldError('receiver.streetAddress1') }}
|
||
</p>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.city') }}</label>
|
||
<a-input v-model="form.receiver.city" allow-clear />
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.province') }}</label>
|
||
<a-input v-model="form.receiver.province" allow-clear />
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
class="h5-po__more"
|
||
@click="showReceiverMore = !showReceiverMore"
|
||
>
|
||
{{
|
||
showReceiverMore
|
||
? t('h5.placeOrder.hideMore')
|
||
: t('h5.placeOrder.showMore')
|
||
}}
|
||
</button>
|
||
|
||
<div v-show="showReceiverMore" class="h5-po__more-panel">
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.company') }}</label>
|
||
<a-input v-model="form.receiver.companyName" allow-clear />
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.telePhone') }}</label>
|
||
<a-input v-model="form.receiver.telePhone" allow-clear />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.email') }}</label>
|
||
<a-input v-model="form.receiver.email" allow-clear />
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.streetNumber') }}</label>
|
||
<a-input v-model="form.receiver.streetNumber" allow-clear />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.address2') }}</label>
|
||
<a-input v-model="form.receiver.streetAddress2" allow-clear />
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.address3') }}</label>
|
||
<a-input v-model="form.receiver.streetAddress3" allow-clear />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.taxNo') }}</label>
|
||
<a-input v-model="form.receiver.taxNo" allow-clear />
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.certificateNo') }}</label>
|
||
<a-input v-model="form.receiver.certificateNo" allow-clear />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.eori') }}</label>
|
||
<a-input v-model="form.receiver.eori" allow-clear />
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.vat') }}</label>
|
||
<a-input v-model="form.receiver.vat" allow-clear />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section v-show="step === 2" class="h5-po__declare">
|
||
<p v-if="declarationLiveHint" class="h5-po__field-err h5-po__field-err--block">
|
||
{{ declarationLiveHint }}
|
||
</p>
|
||
<div class="h5-po__card h5-po__card--currency">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.currency') }}</label>
|
||
<a-select
|
||
v-model="form.declaredCurrency"
|
||
allow-clear
|
||
:error="fieldHasError('declaredCurrency')"
|
||
>
|
||
<a-option v-for="opt in currencyOptions" :key="opt.value" :value="opt.value">
|
||
{{ opt.label }}
|
||
</a-option>
|
||
</a-select>
|
||
<p v-if="fieldError('declaredCurrency')" class="h5-po__field-err">
|
||
{{ fieldError('declaredCurrency') }}
|
||
</p>
|
||
</div>
|
||
|
||
<p v-if="fieldError('itemsEmpty')" class="h5-po__field-err h5-po__field-err--block">
|
||
{{ fieldError('itemsEmpty') }}
|
||
</p>
|
||
|
||
<div v-for="(row, idx) in items" :key="row._key" class="h5-po__card h5-po__item">
|
||
<div class="h5-po__item-head">
|
||
<div class="h5-po__item-head-main">
|
||
<strong>{{ t('h5.placeOrder.itemTitle', { n: idx + 1 }) }}</strong>
|
||
<span class="h5-po__item-derived">
|
||
{{ t('h5.placeOrder.amount') }} {{ row.amount || '-' }} ·
|
||
{{ t('h5.placeOrder.itemWeight') }} {{ row.weight || '-' }}
|
||
</span>
|
||
</div>
|
||
<button
|
||
v-if="items.length > 1"
|
||
type="button"
|
||
class="h5-po__item-remove"
|
||
@click="removeItem(idx)"
|
||
>
|
||
{{ t('h5.placeOrder.removeItem') }}
|
||
</button>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.enName') }}</label>
|
||
<a-input
|
||
v-model="row.enName"
|
||
allow-clear
|
||
:error="fieldHasError(`items.${idx}.enName`)"
|
||
/>
|
||
<p v-if="fieldError(`items.${idx}.enName`)" class="h5-po__field-err">
|
||
{{ fieldError(`items.${idx}.enName`) }}
|
||
</p>
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.cnName') }}</label>
|
||
<a-input v-model="row.cnName" allow-clear />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__three">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.unitPrice') }}</label>
|
||
<a-input
|
||
v-model="row.unitPrice"
|
||
allow-clear
|
||
:error="fieldHasError(`items.${idx}.unitPrice`)"
|
||
@input="syncItemDerived(row)"
|
||
/>
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.unitWeight') }}</label>
|
||
<a-input
|
||
v-model="row.unitWeight"
|
||
allow-clear
|
||
:error="fieldHasError(`items.${idx}.unitWeight`)"
|
||
@input="syncItemDerived(row)"
|
||
/>
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.qty') }}</label>
|
||
<a-input
|
||
v-model="row.quantity"
|
||
allow-clear
|
||
:error="fieldHasError(`items.${idx}.quantity`)"
|
||
@input="syncItemDerived(row)"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<p
|
||
v-if="
|
||
fieldError(`items.${idx}.unitPrice`) ||
|
||
fieldError(`items.${idx}.unitWeight`) ||
|
||
fieldError(`items.${idx}.quantity`)
|
||
"
|
||
class="h5-po__field-err"
|
||
>
|
||
{{
|
||
fieldError(`items.${idx}.unitPrice`) ||
|
||
fieldError(`items.${idx}.unitWeight`) ||
|
||
fieldError(`items.${idx}.quantity`)
|
||
}}
|
||
</p>
|
||
|
||
<button
|
||
type="button"
|
||
class="h5-po__more"
|
||
@click="toggleItemMore(row._key)"
|
||
>
|
||
{{
|
||
isItemMoreOpen(row._key)
|
||
? t('h5.placeOrder.hideMore')
|
||
: t('h5.placeOrder.showMore')
|
||
}}
|
||
</button>
|
||
|
||
<div v-show="isItemMoreOpen(row._key)" class="h5-po__more-panel">
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.sku') }}</label>
|
||
<a-input v-model="row.sku" allow-clear />
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.unit') }}</label>
|
||
<a-input v-model="row.unit" allow-clear />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.imHsCode') }}</label>
|
||
<a-input v-model="row.imHsCode" allow-clear />
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.exHsCode') }}</label>
|
||
<a-input v-model="row.exHsCode" allow-clear />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.brand') }}</label>
|
||
<a-input v-model="row.brand" allow-clear />
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.model') }}</label>
|
||
<a-input v-model="row.model" allow-clear />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.material') }}</label>
|
||
<a-input v-model="row.material" allow-clear />
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.enMaterial') }}</label>
|
||
<a-input v-model="row.enMaterial" allow-clear />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.purpose') }}</label>
|
||
<a-input v-model="row.purpose" allow-clear />
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.originCountry') }}</label>
|
||
<a-input v-model="row.originCountry" allow-clear />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__row">
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.category') }}</label>
|
||
<a-input v-model="row.category" allow-clear />
|
||
</div>
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.specifications') }}</label>
|
||
<a-input v-model="row.specifications" allow-clear />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.salesLink') }}</label>
|
||
<a-input v-model="row.salesLink" allow-clear />
|
||
</div>
|
||
|
||
<div class="h5-po__field">
|
||
<label class="h5-po__label">{{ t('h5.placeOrder.comment') }}</label>
|
||
<a-textarea v-model="row.comment" :auto-size="{ minRows: 2, maxRows: 3 }" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button type="button" class="h5-po__add-item" @click="addItem">
|
||
+ {{ t('h5.placeOrder.addItem') }}
|
||
</button>
|
||
</section>
|
||
</a-spin>
|
||
|
||
<footer class="h5-po__foot">
|
||
<button
|
||
v-if="step > 0"
|
||
type="button"
|
||
class="h5-po__btn is-ghost"
|
||
:disabled="submitting"
|
||
@click="step -= 1"
|
||
>
|
||
{{ t('h5.placeOrder.prev') }}
|
||
</button>
|
||
<button
|
||
v-if="step < 2"
|
||
type="button"
|
||
class="h5-po__btn is-primary"
|
||
@click="goNext"
|
||
>
|
||
{{ t('h5.placeOrder.next') }}
|
||
</button>
|
||
<button
|
||
v-else
|
||
type="button"
|
||
class="h5-po__btn is-primary"
|
||
:disabled="submitting"
|
||
@click="submit"
|
||
>
|
||
<span v-if="submitting" class="h5-po__loading" aria-hidden="true" />
|
||
{{ submitting ? t('h5.placeOrder.submitting') : t('h5.placeOrder.submit') }}
|
||
</button>
|
||
</footer>
|
||
</div>
|
||
</a-drawer>
|
||
|
||
<a-modal
|
||
v-model:visible="createFailVisible"
|
||
:title="
|
||
createFailInfo?.draftSaved
|
||
? t('h5.placeOrder.failTitleDraft')
|
||
: t('h5.placeOrder.failTitle')
|
||
"
|
||
:footer="false"
|
||
:mask-closable="!createFailCancelling && !createFailSavingDraft"
|
||
unmount-on-close
|
||
width="92%"
|
||
>
|
||
<div v-if="createFailInfo" class="h5-po-fail">
|
||
<div class="h5-po-fail__hero">
|
||
<p class="h5-po-fail__reason">{{ createFailInfo.reason }}</p>
|
||
<p v-if="createFailInfo.tips?.[0]" class="h5-po-fail__tip">
|
||
<span class="h5-po-fail__tip-label">{{ t('h5.placeOrder.failHow') }}</span>
|
||
{{ createFailInfo.tips[0] }}
|
||
</p>
|
||
</div>
|
||
|
||
<div v-if="createFailInfo.draftSaved" class="h5-po-fail__status">
|
||
<span>{{ t('h5.placeOrder.failDraftNote') }}</span>
|
||
<span v-if="createFailInfo.serviceNo" class="h5-po-fail__sno">
|
||
{{ createFailInfo.serviceNo }}
|
||
</span>
|
||
</div>
|
||
|
||
<details
|
||
v-if="createFailInfo.rawReason && createFailInfo.rawReason !== createFailInfo.reason"
|
||
class="h5-po-fail__details"
|
||
>
|
||
<summary>{{ t('h5.placeOrder.failRaw') }}</summary>
|
||
<p class="h5-po-fail__raw">{{ createFailInfo.rawReason }}</p>
|
||
</details>
|
||
|
||
<div class="h5-po-fail__actions">
|
||
<button
|
||
type="button"
|
||
class="h5-po__btn is-ghost"
|
||
:disabled="createFailCancelling || createFailSavingDraft"
|
||
@click="handleCreateFailCancel"
|
||
>
|
||
{{
|
||
createFailInfo.draftSaved
|
||
? t('h5.placeOrder.failCancelDraft')
|
||
: t('h5.placeOrder.failAbandon')
|
||
}}
|
||
</button>
|
||
<button
|
||
v-if="!createFailInfo.draftSaved"
|
||
type="button"
|
||
class="h5-po__btn is-ghost"
|
||
:disabled="createFailCancelling || createFailSavingDraft"
|
||
@click="handleCreateFailSaveDraft"
|
||
>
|
||
{{
|
||
createFailSavingDraft
|
||
? t('h5.placeOrder.submitting')
|
||
: t('h5.placeOrder.failSaveDraft')
|
||
}}
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="h5-po__btn is-primary"
|
||
:disabled="createFailCancelling || createFailSavingDraft"
|
||
@click="handleCreateFailPrimary"
|
||
>
|
||
{{
|
||
createFailInfo.draftSaved
|
||
? t('h5.placeOrder.failGoDraft')
|
||
: t('h5.placeOrder.failContinue')
|
||
}}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</a-modal>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { computed, reactive, ref, watch } from 'vue';
|
||
import { useRouter } from 'vue-router';
|
||
import { useI18n } from 'vue-i18n';
|
||
import { Message, Modal } from '@arco-design/web-vue';
|
||
import { IconClose } from '@arco-design/web-vue/es/icon';
|
||
import { postOrderDelete, postOrderParcelCreate } from '@/api/order';
|
||
import { createSenderAddress, getSenderAddressList } from '@/api/order/senderAddress';
|
||
import { getSalesProductList } from '@/api/channel/salesProduct';
|
||
import { getDictListAll } from '@/api/system/dict';
|
||
import { getCountryList } from '@/api/system/country';
|
||
import { getWarehouseList } from '@/api/system/warehouse';
|
||
import { normalizeCargoAttributeCode } from '@/utils/cargoAttribute';
|
||
import { pickField } from '@/utils/warehouseListMapper';
|
||
import { h5Navigate } from '@/utils/device';
|
||
import { isCustomerContactRequiredError } from '@/utils/customerContactGate';
|
||
import {
|
||
buildParcelCreateFailGuide,
|
||
type ParcelCreateFailGuide,
|
||
} from '@/utils/parcelCreateFailGuide';
|
||
|
||
function isSenderRequiredError(error: unknown): boolean {
|
||
const msg = String(
|
||
(error as any)?.response?.data?.message ||
|
||
(error as any)?.response?.data?.Message ||
|
||
(error as any)?.message ||
|
||
error ||
|
||
'',
|
||
);
|
||
return msg.includes('发件人姓名不能为空') || /sender\s*name/i.test(msg);
|
||
}
|
||
|
||
defineOptions({ name: 'H5PlaceOrderSheet' });
|
||
|
||
const PRICE_CHECK_ORDER_DRAFT_KEY = 'oms_price_check_order_draft';
|
||
|
||
const props = defineProps<{
|
||
visible: boolean;
|
||
/** 查价带入草稿;不传则尝试读本地缓存 */
|
||
draft?: Record<string, any> | null;
|
||
}>();
|
||
|
||
const emit = defineEmits<{
|
||
'update:visible': [value: boolean];
|
||
success: [];
|
||
}>();
|
||
|
||
const { t } = useI18n();
|
||
const router = useRouter();
|
||
|
||
const step = ref(0);
|
||
const optionsLoading = ref(false);
|
||
const senderLoading = ref(false);
|
||
const showSenderForm = ref(false);
|
||
const savingSender = ref(false);
|
||
const showReceiverMore = ref(false);
|
||
/** 申报品「更多」展开状态,按 item._key */
|
||
const openItemMoreKeys = ref<Set<string>>(new Set());
|
||
const senderForm = reactive({
|
||
senderName: '',
|
||
phone: '',
|
||
province: '',
|
||
city: '',
|
||
streetAddress1: '',
|
||
countryCode: 'CN',
|
||
});
|
||
const submitting = ref(false);
|
||
const handoffTip = ref('');
|
||
const selectedSenderId = ref<number | undefined>();
|
||
/** 当前步骤校验失败后标红;申报步骤始终即时标红 */
|
||
const showFieldErrors = ref(false);
|
||
|
||
const drawerStyle = {
|
||
borderRadius: '20px 20px 0 0',
|
||
overflow: 'hidden',
|
||
maxHeight: 'min(92vh, 780px)',
|
||
};
|
||
|
||
const steps = computed(() => [
|
||
{ key: 'basic', label: t('h5.placeOrder.stepBasic') },
|
||
{ key: 'receiver', label: t('h5.placeOrder.stepReceiver') },
|
||
{ key: 'declaration', label: t('h5.placeOrder.stepDeclaration') },
|
||
]);
|
||
|
||
const productOptions = ref<{ value: string; label: string }[]>([]);
|
||
const cargoOptions = ref<{ value: string; label: string }[]>([]);
|
||
const warehouseOptions = ref<{ value: string; label: string }[]>([]);
|
||
const countryOptions = ref<{ value: string; label: string }[]>([]);
|
||
const currencyOptions = ref<{ value: string; label: string }[]>([]);
|
||
const senderOptions = ref<{ id: number; label: string; data: Record<string, string> }[]>([]);
|
||
|
||
type DeclareItem = {
|
||
_key: string;
|
||
cnName: string;
|
||
enName: string;
|
||
unitPrice: string;
|
||
unitWeight: string;
|
||
quantity: string;
|
||
amount: string;
|
||
weight: string;
|
||
sku: string;
|
||
unit: string;
|
||
imHsCode: string;
|
||
exHsCode: string;
|
||
brand: string;
|
||
model: string;
|
||
material: string;
|
||
enMaterial: string;
|
||
purpose: string;
|
||
originCountry: string;
|
||
category: string;
|
||
specifications: string;
|
||
salesLink: string;
|
||
comment: string;
|
||
};
|
||
|
||
let itemKeySeq = 0;
|
||
function createEmptyItem(): DeclareItem {
|
||
itemKeySeq += 1;
|
||
return {
|
||
_key: `item-${itemKeySeq}`,
|
||
cnName: '',
|
||
enName: '',
|
||
unitPrice: '',
|
||
unitWeight: '',
|
||
quantity: '1',
|
||
amount: '',
|
||
weight: '',
|
||
sku: '',
|
||
unit: 'pcs',
|
||
imHsCode: '',
|
||
exHsCode: '',
|
||
brand: '',
|
||
model: '',
|
||
material: '',
|
||
enMaterial: '',
|
||
purpose: '',
|
||
originCountry: '',
|
||
category: '',
|
||
specifications: '',
|
||
salesLink: '',
|
||
comment: '',
|
||
};
|
||
}
|
||
|
||
const items = ref<DeclareItem[]>([createEmptyItem()]);
|
||
|
||
const form = reactive({
|
||
clientNo: '',
|
||
productCode: '',
|
||
cargoAttribute: '',
|
||
exportCustomsMode: '9610Simple',
|
||
warehouseId: '',
|
||
length: '10',
|
||
width: '10',
|
||
height: '10',
|
||
actualWeight: '',
|
||
quantity: '1',
|
||
declaredCurrency: 'USD',
|
||
isCod: false,
|
||
receiver: {
|
||
recipientsName: '',
|
||
phone: '',
|
||
countryCode: '',
|
||
province: '',
|
||
city: '',
|
||
streetAddress1: '',
|
||
zipCode: '',
|
||
companyName: '',
|
||
telePhone: '',
|
||
email: '',
|
||
streetNumber: '',
|
||
streetAddress2: '',
|
||
streetAddress3: '',
|
||
taxType: '0',
|
||
taxNo: '',
|
||
certificateType: '0',
|
||
certificateNo: '',
|
||
eori: '',
|
||
vat: '',
|
||
},
|
||
sender: {
|
||
senderName: '',
|
||
company: '',
|
||
phone: '',
|
||
telePhone: '',
|
||
email: '',
|
||
countryCode: '',
|
||
province: '',
|
||
city: '',
|
||
zipCode: '',
|
||
streetAddress1: '',
|
||
streetAddress2: '',
|
||
streetAddress3: '',
|
||
houseNumber: '',
|
||
},
|
||
});
|
||
|
||
function trim(v: unknown) {
|
||
return String(v ?? '').trim();
|
||
}
|
||
|
||
function parseNum(v: unknown) {
|
||
const n = parseFloat(String(v ?? '').trim());
|
||
return Number.isFinite(n) ? n : undefined;
|
||
}
|
||
|
||
function syncItemDerived(row: DeclareItem) {
|
||
const unitPrice = parseNum(row.unitPrice);
|
||
const unitWeight = parseNum(row.unitWeight);
|
||
const quantity = parseNum(row.quantity);
|
||
row.amount =
|
||
unitPrice != null && quantity != null ? String(Number((unitPrice * quantity).toFixed(4))) : '';
|
||
row.weight =
|
||
unitWeight != null && quantity != null ? String(Number((unitWeight * quantity).toFixed(4))) : '';
|
||
}
|
||
|
||
function addItem() {
|
||
items.value.push(createEmptyItem());
|
||
}
|
||
|
||
function removeItem(idx: number) {
|
||
if (items.value.length <= 1) return;
|
||
const removed = items.value[idx];
|
||
items.value.splice(idx, 1);
|
||
if (removed?._key) {
|
||
const next = new Set(openItemMoreKeys.value);
|
||
next.delete(removed._key);
|
||
openItemMoreKeys.value = next;
|
||
}
|
||
}
|
||
|
||
function isItemMoreOpen(key: string) {
|
||
return openItemMoreKeys.value.has(key);
|
||
}
|
||
|
||
function toggleItemMore(key: string) {
|
||
const next = new Set(openItemMoreKeys.value);
|
||
if (next.has(key)) next.delete(key);
|
||
else next.add(key);
|
||
openItemMoreKeys.value = next;
|
||
}
|
||
|
||
function validateDeclareItem(row: DeclareItem): string | null {
|
||
if (!trim(row.enName)) return t('h5.placeOrder.err.enName');
|
||
if (parseNum(row.unitPrice) == null) return t('h5.placeOrder.err.unitPrice');
|
||
if (parseNum(row.unitWeight) == null) return t('h5.placeOrder.err.unitWeight');
|
||
if (parseNum(row.quantity) == null) return t('h5.placeOrder.err.qty');
|
||
return null;
|
||
}
|
||
|
||
function collectFieldErrors(idx: number): Record<string, string> {
|
||
const errors: Record<string, string> = {};
|
||
if (idx === 0) {
|
||
if (!trim(form.sender.senderName)) {
|
||
errors.sender = senderOptions.value.length
|
||
? t('h5.placeOrder.err.sender')
|
||
: t('h5.placeOrder.err.senderEmpty');
|
||
}
|
||
if (!trim(form.clientNo)) errors.clientNo = t('h5.placeOrder.err.clientNo');
|
||
if (!trim(form.productCode)) errors.productCode = t('h5.placeOrder.err.product');
|
||
if (!trim(form.cargoAttribute)) errors.cargoAttribute = t('h5.placeOrder.err.cargo');
|
||
if (!trim(form.warehouseId) || parseNum(form.warehouseId) == null) {
|
||
errors.warehouseId = t('h5.placeOrder.err.warehouse');
|
||
}
|
||
if (parseNum(form.actualWeight) == null) errors.actualWeight = t('h5.placeOrder.err.weight');
|
||
if ((parseNum(form.length) ?? 0) <= 0) errors.length = t('h5.placeOrder.err.length');
|
||
if ((parseNum(form.width) ?? 0) <= 0) errors.width = t('h5.placeOrder.err.width');
|
||
if ((parseNum(form.height) ?? 0) <= 0) errors.height = t('h5.placeOrder.err.height');
|
||
}
|
||
if (idx === 1) {
|
||
if (!trim(form.receiver.recipientsName)) {
|
||
errors['receiver.recipientsName'] = t('h5.placeOrder.err.receiverName');
|
||
}
|
||
if (!trim(form.receiver.phone)) errors['receiver.phone'] = t('h5.placeOrder.err.receiverPhone');
|
||
if (!trim(form.receiver.countryCode)) {
|
||
errors['receiver.countryCode'] = t('h5.placeOrder.err.country');
|
||
}
|
||
if (!trim(form.receiver.streetAddress1)) {
|
||
errors['receiver.streetAddress1'] = t('h5.placeOrder.err.address');
|
||
}
|
||
if (!trim(form.receiver.zipCode)) errors['receiver.zipCode'] = t('h5.placeOrder.err.zip');
|
||
}
|
||
if (idx === 2) {
|
||
if (!trim(form.declaredCurrency)) errors.declaredCurrency = t('h5.placeOrder.err.currency');
|
||
if (!items.value.length) errors.itemsEmpty = t('h5.placeOrder.err.itemsEmpty');
|
||
items.value.forEach((row, i) => {
|
||
if (!trim(row.enName)) errors[`items.${i}.enName`] = t('h5.placeOrder.err.enName');
|
||
if (parseNum(row.unitPrice) == null) {
|
||
errors[`items.${i}.unitPrice`] = t('h5.placeOrder.err.unitPrice');
|
||
}
|
||
if (parseNum(row.unitWeight) == null) {
|
||
errors[`items.${i}.unitWeight`] = t('h5.placeOrder.err.unitWeight');
|
||
}
|
||
if (parseNum(row.quantity) == null) errors[`items.${i}.quantity`] = t('h5.placeOrder.err.qty');
|
||
});
|
||
}
|
||
return errors;
|
||
}
|
||
|
||
function isDeclareRowStarted(row: DeclareItem) {
|
||
return Boolean(
|
||
trim(row.enName) ||
|
||
trim(row.cnName) ||
|
||
trim(row.unitPrice) ||
|
||
trim(row.unitWeight) ||
|
||
trim(row.quantity),
|
||
);
|
||
}
|
||
|
||
const activeFieldErrors = computed(() => {
|
||
if (step.value === 2) {
|
||
const all = collectFieldErrors(2);
|
||
if (showFieldErrors.value) return all;
|
||
const filtered: Record<string, string> = {};
|
||
Object.entries(all).forEach(([key, msg]) => {
|
||
if (key === 'declaredCurrency') {
|
||
filtered[key] = msg;
|
||
return;
|
||
}
|
||
const match = /^items\.(\d+)\./.exec(key);
|
||
if (!match) return;
|
||
const row = items.value[Number(match[1])];
|
||
if (row && isDeclareRowStarted(row)) filtered[key] = msg;
|
||
});
|
||
return filtered;
|
||
}
|
||
if (!showFieldErrors.value) return {} as Record<string, string>;
|
||
return collectFieldErrors(step.value);
|
||
});
|
||
|
||
const declarationLiveHint = computed(() => {
|
||
if (step.value !== 2 || showFieldErrors.value) return '';
|
||
return firstFieldErrorMessage(2) || '';
|
||
});
|
||
|
||
function fieldHasError(key: string) {
|
||
return Boolean(activeFieldErrors.value[key]);
|
||
}
|
||
|
||
function fieldError(key: string) {
|
||
return activeFieldErrors.value[key] || '';
|
||
}
|
||
|
||
function firstFieldErrorMessage(idx: number): string | null {
|
||
if (idx === 2) {
|
||
if (!trim(form.declaredCurrency)) return t('h5.placeOrder.err.currency');
|
||
if (!items.value.length) return t('h5.placeOrder.err.itemsEmpty');
|
||
for (let i = 0; i < items.value.length; i += 1) {
|
||
const msg = validateDeclareItem(items.value[i]);
|
||
if (msg) return t('h5.placeOrder.err.itemLine', { n: i + 1, msg });
|
||
}
|
||
return null;
|
||
}
|
||
const errors = collectFieldErrors(idx);
|
||
return Object.values(errors)[0] || null;
|
||
}
|
||
|
||
function resetForm() {
|
||
step.value = 0;
|
||
handoffTip.value = '';
|
||
showFieldErrors.value = false;
|
||
showReceiverMore.value = false;
|
||
openItemMoreKeys.value = new Set();
|
||
selectedSenderId.value = undefined;
|
||
items.value = [createEmptyItem()];
|
||
form.clientNo = '';
|
||
form.productCode = '';
|
||
form.cargoAttribute = '';
|
||
form.exportCustomsMode = '9610Simple';
|
||
form.warehouseId = '';
|
||
form.length = '10';
|
||
form.width = '10';
|
||
form.height = '10';
|
||
form.actualWeight = '';
|
||
form.quantity = '1';
|
||
form.declaredCurrency = 'USD';
|
||
Object.keys(form.receiver).forEach((k) => {
|
||
(form.receiver as any)[k] = k === 'taxType' || k === 'certificateType' ? '0' : '';
|
||
});
|
||
Object.keys(form.sender).forEach((k) => {
|
||
(form.sender as any)[k] = '';
|
||
});
|
||
}
|
||
|
||
function ensureClientNo() {
|
||
if (trim(form.clientNo)) return;
|
||
const now = new Date();
|
||
const date = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
||
form.clientNo = `C${date}${Math.random().toString().slice(2, 8)}`;
|
||
}
|
||
|
||
function validateStep(idx: number): string | null {
|
||
return firstFieldErrorMessage(idx);
|
||
}
|
||
|
||
function goNext() {
|
||
const err = validateStep(step.value);
|
||
if (err) {
|
||
showFieldErrors.value = true;
|
||
if (!trim(form.sender.senderName) && !senderOptions.value.length) {
|
||
showSenderForm.value = true;
|
||
}
|
||
Message.warning(err);
|
||
return;
|
||
}
|
||
showFieldErrors.value = false;
|
||
step.value += 1;
|
||
}
|
||
|
||
function goToStep(idx: number) {
|
||
if (idx === step.value || submitting.value) return;
|
||
if (idx < step.value) {
|
||
showFieldErrors.value = false;
|
||
step.value = idx;
|
||
return;
|
||
}
|
||
for (let i = step.value; i < idx; i += 1) {
|
||
const err = validateStep(i);
|
||
if (err) {
|
||
showFieldErrors.value = true;
|
||
if (i === 0 && !trim(form.sender.senderName) && !senderOptions.value.length) {
|
||
showSenderForm.value = true;
|
||
}
|
||
step.value = i;
|
||
Message.warning(err);
|
||
return;
|
||
}
|
||
}
|
||
showFieldErrors.value = false;
|
||
step.value = idx;
|
||
}
|
||
|
||
function resetSenderForm() {
|
||
Object.assign(senderForm, {
|
||
senderName: '',
|
||
phone: '',
|
||
province: '',
|
||
city: '',
|
||
streetAddress1: '',
|
||
countryCode: 'CN',
|
||
});
|
||
}
|
||
|
||
async function saveSender() {
|
||
if (!trim(senderForm.senderName) || !trim(senderForm.streetAddress1)) {
|
||
Message.warning(t('h5.placeOrder.err.senderForm'));
|
||
return;
|
||
}
|
||
savingSender.value = true;
|
||
try {
|
||
await createSenderAddress({
|
||
addressType: 'sender',
|
||
addressName: trim(senderForm.senderName),
|
||
senderName: trim(senderForm.senderName),
|
||
phone: trim(senderForm.phone),
|
||
countryCode: trim(senderForm.countryCode) || 'CN',
|
||
province: trim(senderForm.province),
|
||
city: trim(senderForm.city),
|
||
streetAddress1: trim(senderForm.streetAddress1),
|
||
isDefault: senderOptions.value.length === 0,
|
||
});
|
||
Message.success(t('h5.placeOrder.senderSaved'));
|
||
showSenderForm.value = false;
|
||
resetSenderForm();
|
||
await loadSenders();
|
||
} catch (e: any) {
|
||
Message.error(e?.message || t('h5.placeOrder.senderSaveFailed'));
|
||
} finally {
|
||
savingSender.value = false;
|
||
}
|
||
}
|
||
|
||
function onSenderChange(id: number | undefined) {
|
||
selectedSenderId.value = id;
|
||
const hit = senderOptions.value.find((x) => x.id === id);
|
||
if (!hit) {
|
||
Object.keys(form.sender).forEach((k) => {
|
||
(form.sender as any)[k] = '';
|
||
});
|
||
return;
|
||
}
|
||
Object.assign(form.sender, hit.data);
|
||
}
|
||
|
||
function buildPayload() {
|
||
items.value.forEach((row) => syncItemDerived(row));
|
||
const payloadItems = items.value.map((row) => {
|
||
const { _key, ...rest } = row;
|
||
return {
|
||
...rest,
|
||
unitPrice: parseNum(row.unitPrice) ?? 0,
|
||
unitWeight: parseNum(row.unitWeight) ?? 0,
|
||
quantity: parseNum(row.quantity) ?? 0,
|
||
amount: parseNum(row.amount) ?? 0,
|
||
weight: parseNum(row.weight) ?? 0,
|
||
isMagnetic: false,
|
||
};
|
||
});
|
||
const declaredSum = payloadItems.reduce((sum, row) => sum + (Number(row.amount) || 0), 0);
|
||
return {
|
||
clientNo: trim(form.clientNo),
|
||
productCode: trim(form.productCode),
|
||
cargoAttribute: normalizeCargoAttributeCode(form.cargoAttribute),
|
||
exportCustomsMode: trim(form.exportCustomsMode) || '9610Simple',
|
||
warehouseId: parseNum(form.warehouseId) ?? 0,
|
||
length: parseNum(form.length),
|
||
width: parseNum(form.width),
|
||
height: parseNum(form.height),
|
||
actualWeight: parseNum(form.actualWeight) ?? 0,
|
||
quantity: trim(form.quantity) || '1',
|
||
declaredCurrency: trim(form.declaredCurrency),
|
||
declaredSum,
|
||
isCod: false,
|
||
receiver: {
|
||
...form.receiver,
|
||
countryCode: trim(form.receiver.countryCode).toUpperCase(),
|
||
taxType: 0,
|
||
certificateType: 0,
|
||
},
|
||
sender: {
|
||
...form.sender,
|
||
countryCode: trim(form.sender.countryCode).toUpperCase() || undefined,
|
||
},
|
||
items: payloadItems,
|
||
};
|
||
}
|
||
|
||
function clearDraftStorage() {
|
||
try {
|
||
sessionStorage.removeItem(PRICE_CHECK_ORDER_DRAFT_KEY);
|
||
localStorage.removeItem(PRICE_CHECK_ORDER_DRAFT_KEY);
|
||
} catch {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
function close() {
|
||
if (submitting.value) return;
|
||
emit('update:visible', false);
|
||
}
|
||
|
||
async function submit() {
|
||
for (let i = 0; i <= 2; i += 1) {
|
||
const err = validateStep(i);
|
||
if (err) {
|
||
step.value = i;
|
||
showFieldErrors.value = true;
|
||
if (i === 0 && !trim(form.sender.senderName) && !senderOptions.value.length) {
|
||
showSenderForm.value = true;
|
||
}
|
||
Message.warning(err);
|
||
return;
|
||
}
|
||
}
|
||
submitting.value = true;
|
||
try {
|
||
await postOrderParcelCreate(buildPayload());
|
||
clearDraftStorage();
|
||
emit('success');
|
||
Modal.confirm({
|
||
title: t('h5.placeOrder.successTitle'),
|
||
content: t('h5.placeOrder.successContent'),
|
||
okText: t('h5.placeOrder.goOrders'),
|
||
cancelText: t('h5.placeOrder.stay'),
|
||
onOk: () => {
|
||
emit('update:visible', false);
|
||
h5Navigate(router, '/h5/orders');
|
||
},
|
||
onCancel: () => {
|
||
// 继续下单:重置表单留在抽屉
|
||
resetForm();
|
||
ensureClientNo();
|
||
void loadOptions();
|
||
void loadSenders();
|
||
},
|
||
});
|
||
} catch (e: any) {
|
||
if (isCustomerContactRequiredError(e)) {
|
||
Message.warning(t('h5.contact.requiredToast'));
|
||
} else if (isSenderRequiredError(e)) {
|
||
step.value = 0;
|
||
if (!senderOptions.value.length) showSenderForm.value = true;
|
||
Message.warning(
|
||
senderOptions.value.length
|
||
? t('h5.placeOrder.err.sender')
|
||
: t('h5.placeOrder.err.senderEmpty'),
|
||
);
|
||
} else {
|
||
openCreateFailModal(e);
|
||
}
|
||
} finally {
|
||
submitting.value = false;
|
||
}
|
||
}
|
||
|
||
const createFailVisible = ref(false);
|
||
const createFailCancelling = ref(false);
|
||
const createFailSavingDraft = ref(false);
|
||
const createFailInfo = ref<ParcelCreateFailGuide | null>(null);
|
||
|
||
function openCreateFailModal(error: unknown) {
|
||
createFailInfo.value = buildParcelCreateFailGuide(error);
|
||
createFailVisible.value = true;
|
||
}
|
||
|
||
function focusFailSection(section: string) {
|
||
if (section === 'recipient') step.value = 1;
|
||
else if (section === 'declaration') step.value = 2;
|
||
else {
|
||
step.value = 0;
|
||
if (section === 'sender' && !senderOptions.value.length) showSenderForm.value = true;
|
||
}
|
||
showFieldErrors.value = true;
|
||
}
|
||
|
||
function handleCreateFailPrimary() {
|
||
const info = createFailInfo.value;
|
||
createFailVisible.value = false;
|
||
if (!info) return;
|
||
if (info.draftSaved && info.orderId) {
|
||
emit('update:visible', false);
|
||
h5Navigate(router, {
|
||
path: '/h5/orders',
|
||
query: { orderId: String(info.orderId) },
|
||
});
|
||
return;
|
||
}
|
||
focusFailSection(info.section);
|
||
}
|
||
|
||
async function handleCreateFailSaveDraft() {
|
||
const info = createFailInfo.value;
|
||
if (!info || info.draftSaved || createFailSavingDraft.value) return;
|
||
|
||
createFailSavingDraft.value = true;
|
||
try {
|
||
const created = await postOrderParcelCreate({
|
||
...buildPayload(),
|
||
saveAsDraft: true,
|
||
});
|
||
const orderId = Number(created?.orderId ?? created?.OrderId ?? created?.id ?? created?.Id ?? 0) || undefined;
|
||
createFailVisible.value = false;
|
||
clearDraftStorage();
|
||
Modal.confirm({
|
||
title: t('h5.placeOrder.failSaveDraftOk'),
|
||
content: t('h5.placeOrder.failDraftNote'),
|
||
okText: t('h5.placeOrder.failSaveDraftGo'),
|
||
cancelText: t('h5.placeOrder.failSaveDraftStay'),
|
||
onOk: () => {
|
||
emit('update:visible', false);
|
||
h5Navigate(router, {
|
||
path: '/h5/orders',
|
||
query: orderId ? { orderId: String(orderId) } : {},
|
||
});
|
||
},
|
||
onCancel: () => {
|
||
resetForm();
|
||
ensureClientNo();
|
||
},
|
||
});
|
||
} catch (e: any) {
|
||
Message.error(e?.message || t('h5.placeOrder.failSaveDraftFailed'));
|
||
} finally {
|
||
createFailSavingDraft.value = false;
|
||
}
|
||
}
|
||
|
||
function handleCreateFailCancel() {
|
||
const info = createFailInfo.value;
|
||
if (!info) {
|
||
createFailVisible.value = false;
|
||
return;
|
||
}
|
||
if (info.draftSaved && info.orderId) {
|
||
Modal.confirm({
|
||
title: t('h5.placeOrder.failCancelDraftTitle'),
|
||
content: t('h5.placeOrder.failCancelDraftContent'),
|
||
okText: t('h5.placeOrder.failCancelDraftOk'),
|
||
cancelText: t('h5.placeOrder.failKeep'),
|
||
okButtonProps: { status: 'danger' },
|
||
onOk: async () => {
|
||
createFailCancelling.value = true;
|
||
try {
|
||
await postOrderDelete({ id: info.orderId! });
|
||
Message.success(t('h5.placeOrder.failCancelDraftDone'));
|
||
createFailVisible.value = false;
|
||
resetForm();
|
||
ensureClientNo();
|
||
} catch (e: any) {
|
||
Message.error(e?.message || t('h5.placeOrder.failCancelDraftFailed'));
|
||
} finally {
|
||
createFailCancelling.value = false;
|
||
}
|
||
},
|
||
});
|
||
return;
|
||
}
|
||
Modal.confirm({
|
||
title: t('h5.placeOrder.failAbandonTitle'),
|
||
content: t('h5.placeOrder.failAbandonContent'),
|
||
okText: t('h5.placeOrder.failAbandonOk'),
|
||
cancelText: t('h5.placeOrder.failKeep'),
|
||
okButtonProps: { status: 'danger' },
|
||
onOk: () => {
|
||
createFailVisible.value = false;
|
||
resetForm();
|
||
ensureClientNo();
|
||
Message.success(t('h5.placeOrder.failAbandonDone'));
|
||
},
|
||
});
|
||
}
|
||
|
||
function readStoredDraft(): Record<string, any> | null {
|
||
try {
|
||
const raw =
|
||
sessionStorage.getItem(PRICE_CHECK_ORDER_DRAFT_KEY) ||
|
||
localStorage.getItem(PRICE_CHECK_ORDER_DRAFT_KEY);
|
||
return raw ? JSON.parse(raw) : null;
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function applyDraft(draft: Record<string, any> | null) {
|
||
if (!draft) return;
|
||
const filled: string[] = [];
|
||
if (draft.productCode || draft.shippingMethod) {
|
||
const code = trim(draft.productCode);
|
||
const byCode = productOptions.value.find((o) => o.value === code);
|
||
const byName = productOptions.value.find(
|
||
(o) => o.label.includes(trim(draft.shippingMethod)) || o.value === trim(draft.shippingMethod),
|
||
);
|
||
form.productCode = byCode?.value || byName?.value || code;
|
||
if (form.productCode) filled.push(t('h5.placeOrder.product'));
|
||
}
|
||
if (draft.cargoAttribute || draft.cargoType) {
|
||
form.cargoAttribute = normalizeCargoAttributeCode(draft.cargoAttribute || draft.cargoType);
|
||
filled.push(t('h5.placeOrder.cargo'));
|
||
}
|
||
if (draft.warehouseId != null && draft.warehouseId !== '') {
|
||
form.warehouseId = String(draft.warehouseId);
|
||
filled.push(t('h5.placeOrder.warehouse'));
|
||
}
|
||
const weight = draft.actualWeight || draft.cargoWeight;
|
||
if (weight) {
|
||
form.actualWeight = String(weight);
|
||
filled.push(t('h5.placeOrder.weight'));
|
||
}
|
||
if (draft.length && draft.width && draft.height) {
|
||
form.length = String(draft.length);
|
||
form.width = String(draft.width);
|
||
form.height = String(draft.height);
|
||
filled.push(t('h5.placeOrder.dimensions'));
|
||
}
|
||
const country = draft.countryCode || draft.destinationCountry;
|
||
if (country) {
|
||
form.receiver.countryCode = String(country).toUpperCase();
|
||
filled.push(t('h5.placeOrder.country'));
|
||
}
|
||
if (draft.zipCode || draft.postalCode) {
|
||
form.receiver.zipCode = String(draft.zipCode || draft.postalCode);
|
||
}
|
||
|
||
handoffTip.value = t('h5.placeOrder.handoffFilled', {
|
||
items: filled.join('、') || '-',
|
||
});
|
||
// 始终从基础信息开始,不因查价带入字段跳到收件
|
||
step.value = 0;
|
||
}
|
||
|
||
async function loadOptions(draft: Record<string, any> | null) {
|
||
optionsLoading.value = true;
|
||
try {
|
||
const [productRes, cargoRows, warehouseRes, currencyRows, countryRes, salesProductGroupDictRows] =
|
||
await Promise.all([
|
||
getSalesProductList({ page: 1, pageSize: 500, isActive: 2 }),
|
||
getDictListAll({ dictType: 'cargo_attribute' }),
|
||
getWarehouseList({ page: 1, pageSize: 500 }),
|
||
getDictListAll({ dictType: 'currency' }),
|
||
getCountryList(),
|
||
getDictListAll({ dictType: 'sales_product_group' }),
|
||
]);
|
||
|
||
const domesticPickupGroupCodes = new Set(
|
||
(salesProductGroupDictRows ?? [])
|
||
.filter((d: any) => String(d?.dictName ?? d?.DictName ?? '').includes('国内揽收'))
|
||
.map((d: any) => String(d?.dictCode ?? d?.DictCode ?? '').trim())
|
||
.filter((code: string) => Boolean(code)),
|
||
);
|
||
|
||
productOptions.value = (productRes.items ?? [])
|
||
.filter((row: any) => {
|
||
const groupCode = String(row?.productGroup ?? row?.ProductGroup ?? '').trim();
|
||
return domesticPickupGroupCodes.size ? !domesticPickupGroupCodes.has(groupCode) : true;
|
||
})
|
||
.map((row: any) => {
|
||
const code = String(row?.productCode ?? row?.ProductCode ?? '').trim();
|
||
const name = String(row?.productName ?? row?.ProductName ?? '').trim();
|
||
return { value: code, label: name ? `${name} ${code}` : code };
|
||
})
|
||
.filter((x) => x.value);
|
||
|
||
cargoOptions.value = (cargoRows ?? [])
|
||
.map((row: any) => ({
|
||
value: String(row?.dictCode ?? row?.DictCode ?? '').trim(),
|
||
label: String(row?.dictName ?? row?.DictName ?? '').trim(),
|
||
}))
|
||
.filter((x) => x.value);
|
||
if (cargoOptions.value.length && !form.cargoAttribute) {
|
||
form.cargoAttribute = cargoOptions.value[0].value;
|
||
}
|
||
|
||
warehouseOptions.value = (warehouseRes.items ?? [])
|
||
.map((row: any) => {
|
||
const id = row?.id ?? row?.Id;
|
||
const name = String(row?.warehouseName ?? row?.WarehouseName ?? '').trim();
|
||
return { value: String(id ?? ''), label: name || String(id) };
|
||
})
|
||
.filter((x) => x.value);
|
||
if (warehouseOptions.value.length && !form.warehouseId) {
|
||
form.warehouseId = warehouseOptions.value[0].value;
|
||
}
|
||
|
||
currencyOptions.value = (currencyRows ?? [])
|
||
.map((row: any) => ({
|
||
value: String(row?.dictCode ?? row?.DictCode ?? '').trim(),
|
||
label: String(row?.dictName ?? row?.DictName ?? row?.dictCode ?? '').trim(),
|
||
}))
|
||
.filter((x) => x.value);
|
||
const usd = currencyOptions.value.find((x) => x.value.toUpperCase() === 'USD');
|
||
form.declaredCurrency = usd?.value || currencyOptions.value[0]?.value || 'USD';
|
||
|
||
countryOptions.value = (countryRes.items ?? [])
|
||
.map((row: any) => {
|
||
const code = String(row?.countryCode ?? row?.CountryCode ?? row?.code ?? '').trim();
|
||
const name = String(row?.countryName ?? row?.CountryName ?? row?.name ?? '').trim();
|
||
return { value: code, label: name ? `${name} ${code}` : code };
|
||
})
|
||
.filter((x) => x.value);
|
||
|
||
applyDraft(draft);
|
||
ensureClientNo();
|
||
} catch (e: any) {
|
||
Message.error(e?.message || t('h5.placeOrder.optionsFailed'));
|
||
} finally {
|
||
optionsLoading.value = false;
|
||
}
|
||
}
|
||
|
||
async function loadSenders() {
|
||
senderLoading.value = true;
|
||
try {
|
||
const res = await getSenderAddressList({ page: 1, pageSize: 200, addressType: 'sender' });
|
||
senderOptions.value = (res.items || [])
|
||
.map((row: Record<string, unknown>) => {
|
||
const id = Number(pickField(row, 'id', 'Id'));
|
||
if (!Number.isFinite(id) || id <= 0) return null;
|
||
const name = String(pickField(row, 'senderName', 'SenderName') || '');
|
||
const isDefault = Boolean(pickField(row, 'isDefault', 'IsDefault'));
|
||
return {
|
||
id,
|
||
label: `${name || `#${id}`}${isDefault ? ` (${t('h5.placeOrder.default')})` : ''}`,
|
||
isDefault,
|
||
data: {
|
||
senderName: String(pickField(row, 'senderName', 'SenderName') || ''),
|
||
company: String(pickField(row, 'company', 'Company') || ''),
|
||
phone: String(pickField(row, 'phone', 'Phone') || ''),
|
||
telePhone: String(pickField(row, 'telePhone', 'TelePhone') || ''),
|
||
email: String(pickField(row, 'email', 'Email') || ''),
|
||
countryCode: String(pickField(row, 'countryCode', 'CountryCode') || ''),
|
||
province: String(pickField(row, 'province', 'Province') || ''),
|
||
city: String(pickField(row, 'city', 'City') || ''),
|
||
zipCode: String(pickField(row, 'zipCode', 'ZipCode') || ''),
|
||
streetAddress1: String(pickField(row, 'streetAddress1', 'StreetAddress1') || ''),
|
||
streetAddress2: String(pickField(row, 'streetAddress2', 'StreetAddress2') || ''),
|
||
streetAddress3: String(pickField(row, 'streetAddress3', 'StreetAddress3') || ''),
|
||
houseNumber: String(pickField(row, 'houseNumber', 'HouseNumber') || ''),
|
||
},
|
||
};
|
||
})
|
||
.filter(Boolean) as typeof senderOptions.value;
|
||
|
||
const def = (senderOptions.value as any[]).find((x) => x.isDefault) || senderOptions.value[0];
|
||
if (def) {
|
||
onSenderChange(def.id);
|
||
showSenderForm.value = false;
|
||
} else {
|
||
onSenderChange(undefined);
|
||
showSenderForm.value = true;
|
||
}
|
||
} catch {
|
||
senderOptions.value = [];
|
||
showSenderForm.value = true;
|
||
} finally {
|
||
senderLoading.value = false;
|
||
}
|
||
}
|
||
|
||
watch(
|
||
() => props.visible,
|
||
(visible) => {
|
||
if (!visible) {
|
||
submitting.value = false;
|
||
showSenderForm.value = false;
|
||
resetSenderForm();
|
||
return;
|
||
}
|
||
resetForm();
|
||
const draft = props.draft ?? readStoredDraft();
|
||
void loadOptions(draft);
|
||
void loadSenders();
|
||
},
|
||
);
|
||
</script>
|
||
|
||
<style lang="less">
|
||
.h5-po-drawer {
|
||
.arco-drawer-content {
|
||
border-radius: 20px 20px 0 0;
|
||
overflow: hidden;
|
||
max-height: min(92vh, 780px);
|
||
}
|
||
|
||
.arco-drawer-body {
|
||
padding: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: min(92vh, 780px);
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<style lang="less" scoped>
|
||
.h5-po {
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: min(92vh, 780px);
|
||
padding-bottom: env(safe-area-inset-bottom, 0px);
|
||
background: #f5f7f8;
|
||
}
|
||
|
||
.h5-po__handle {
|
||
width: 36px;
|
||
height: 4px;
|
||
margin: 10px auto 0;
|
||
border-radius: 999px;
|
||
background: #c9cdd4;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.h5-po__head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
height: 48px;
|
||
flex-shrink: 0;
|
||
|
||
h3 {
|
||
margin: 0;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #1d2129;
|
||
}
|
||
}
|
||
|
||
.h5-po__close {
|
||
position: absolute;
|
||
right: 12px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 36px;
|
||
height: 36px;
|
||
border: none;
|
||
border-radius: 10px;
|
||
background: transparent;
|
||
color: #86909c;
|
||
font-size: 18px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
|
||
&:disabled {
|
||
opacity: 0.5;
|
||
}
|
||
}
|
||
|
||
.h5-po__steps {
|
||
display: flex;
|
||
gap: 4px;
|
||
margin: 0 16px 10px;
|
||
padding: 3px;
|
||
border-radius: 12px;
|
||
background: #eef0f3;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.h5-po__step {
|
||
flex: 1;
|
||
height: 32px;
|
||
border: none;
|
||
border-radius: 10px;
|
||
background: transparent;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: #86909c;
|
||
cursor: pointer;
|
||
|
||
&.is-active,
|
||
&.is-done {
|
||
background: #fff;
|
||
color: #1d2129;
|
||
font-weight: 600;
|
||
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.06);
|
||
}
|
||
}
|
||
|
||
.h5-po__tip {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
margin: 0 16px 10px;
|
||
padding: 10px 12px;
|
||
border-radius: 12px;
|
||
background: color-mix(in srgb, var(--app-primary-color) 10%, #fff);
|
||
color: #4e5969;
|
||
font-size: 12px;
|
||
line-height: 1.45;
|
||
|
||
button {
|
||
border: none;
|
||
background: transparent;
|
||
color: #86909c;
|
||
font-size: 16px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
padding: 0 2px;
|
||
}
|
||
}
|
||
|
||
.h5-po__spin {
|
||
flex: 1;
|
||
min-height: 220px;
|
||
overflow: auto;
|
||
display: block;
|
||
width: 100%;
|
||
padding: 0 16px 12px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.h5-po__item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.h5-po__card--currency {
|
||
.h5-po__label {
|
||
margin-bottom: 6px;
|
||
}
|
||
}
|
||
|
||
.h5-po__declare {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.h5-po__card {
|
||
background: #fff;
|
||
border-radius: 16px;
|
||
padding: 14px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.04);
|
||
|
||
&--currency {
|
||
padding-bottom: 14px;
|
||
}
|
||
|
||
:deep(.arco-input-wrapper),
|
||
:deep(.arco-textarea-wrapper),
|
||
:deep(.arco-select-view-single),
|
||
:deep(.arco-select-view-multiple) {
|
||
background: #f7f8fa !important;
|
||
border: none !important;
|
||
box-shadow: none !important;
|
||
border-radius: 12px;
|
||
min-height: 40px;
|
||
}
|
||
|
||
:deep(.arco-input-wrapper:hover),
|
||
:deep(.arco-textarea-wrapper:hover),
|
||
:deep(.arco-select-view-single:hover) {
|
||
background: #f2f3f5 !important;
|
||
}
|
||
|
||
:deep(.arco-input-wrapper.arco-input-focus),
|
||
:deep(.arco-textarea-wrapper.arco-textarea-focus),
|
||
:deep(.arco-select-view-single.arco-select-view-focus) {
|
||
background: color-mix(in srgb, var(--app-primary-color) 8%, #fff) !important;
|
||
}
|
||
|
||
:deep(.arco-input),
|
||
:deep(.arco-textarea) {
|
||
font-size: 14px;
|
||
color: #1d2129;
|
||
}
|
||
|
||
:deep(.arco-select-view-value),
|
||
:deep(.arco-select-view-input) {
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
|
||
.h5-po__field {
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.h5-po__row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.h5-po__item-head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
|
||
strong {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #1d2129;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.h5-po__item-head-main {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: baseline;
|
||
gap: 6px 10px;
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
.h5-po__item-derived {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: #86909c;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.h5-po__more {
|
||
margin-top: 2px;
|
||
height: 36px;
|
||
border: none;
|
||
border-radius: 10px;
|
||
background: #f2f3f5;
|
||
color: var(--app-primary-color);
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
|
||
&:active {
|
||
opacity: 0.88;
|
||
}
|
||
}
|
||
|
||
.h5-po__more-panel {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
padding-top: 2px;
|
||
}
|
||
|
||
.h5-po__item-remove {
|
||
border: none;
|
||
background: color-mix(in srgb, #f53f3f 10%, #fff);
|
||
color: #f53f3f;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
padding: 4px 10px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.h5-po__add-item {
|
||
height: 44px;
|
||
border: none;
|
||
border-radius: 14px;
|
||
background: color-mix(in srgb, var(--app-primary-color) 10%, #fff);
|
||
color: var(--app-primary-color);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
|
||
&:active {
|
||
opacity: 0.88;
|
||
}
|
||
}
|
||
|
||
.h5-po__label {
|
||
display: block;
|
||
margin: 0 0 6px;
|
||
font-size: 12px;
|
||
line-height: 1.3;
|
||
color: #4e5969;
|
||
}
|
||
|
||
.h5-po__sender-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
|
||
.h5-po__label {
|
||
margin: 0;
|
||
font-size: 13px;
|
||
}
|
||
}
|
||
|
||
.h5-po__link {
|
||
border: none;
|
||
background: transparent;
|
||
padding: 0;
|
||
color: var(--app-primary-color);
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.h5-po__sender-empty {
|
||
margin: 0;
|
||
padding: 10px 12px;
|
||
border-radius: 10px;
|
||
background: #fff7e8;
|
||
color: #ad6800;
|
||
font-size: 12px;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.h5-po__field-err {
|
||
margin: 4px 0 0;
|
||
font-size: 12px;
|
||
line-height: 1.4;
|
||
color: #f53f3f;
|
||
|
||
&--block {
|
||
margin: 0 0 10px;
|
||
padding: 8px 10px;
|
||
border-radius: 8px;
|
||
background: #ffece8;
|
||
}
|
||
}
|
||
|
||
.h5-po__sender-form {
|
||
display: grid;
|
||
gap: 8px;
|
||
}
|
||
|
||
.h5-po__btn--block {
|
||
width: 100%;
|
||
}
|
||
|
||
.h5-po__dims,
|
||
.h5-po__three {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr 1fr;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.h5-po__dims--with-weight {
|
||
grid-template-columns: 1.15fr 1fr 1fr 1fr;
|
||
}
|
||
|
||
.h5-po__foot {
|
||
display: flex;
|
||
gap: 10px;
|
||
padding: 12px 16px calc(14px + env(safe-area-inset-bottom, 0px));
|
||
background: #fff;
|
||
box-shadow: 0 -6px 20px rgba(15, 23, 42, 0.04);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.h5-po-fail {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.h5-po-fail__hero {
|
||
padding: 12px 14px;
|
||
border-radius: 8px;
|
||
border-left: 3px solid #f53f3f;
|
||
background: #fff7f7;
|
||
}
|
||
|
||
.h5-po-fail__reason {
|
||
margin: 0;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
line-height: 1.45;
|
||
color: #1d2129;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.h5-po-fail__tip {
|
||
margin: 8px 0 0;
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
color: #4e5969;
|
||
}
|
||
|
||
.h5-po-fail__tip-label {
|
||
margin-right: 4px;
|
||
color: var(--app-primary-color, #2d8c8c);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.h5-po-fail__status {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
padding: 8px 12px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
line-height: 1.4;
|
||
color: #0fc6c2;
|
||
background: rgba(15, 198, 194, 0.08);
|
||
}
|
||
|
||
.h5-po-fail__sno {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.h5-po-fail__details {
|
||
font-size: 12px;
|
||
color: #86909c;
|
||
|
||
summary {
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
}
|
||
|
||
.h5-po-fail__raw {
|
||
margin: 6px 0 0;
|
||
padding: 8px 10px;
|
||
border-radius: 6px;
|
||
background: #f7f8fa;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
color: #86909c;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.h5-po-fail__actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 4px;
|
||
|
||
.h5-po__btn {
|
||
flex: 1;
|
||
height: 44px;
|
||
}
|
||
}
|
||
|
||
.h5-po__btn {
|
||
flex: 1;
|
||
height: 48px;
|
||
border: none;
|
||
border-radius: 12px;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
|
||
&.is-ghost {
|
||
flex: 0 0 96px;
|
||
background: #f2f3f5;
|
||
color: #4e5969;
|
||
|
||
&:active:not(:disabled) {
|
||
background: #e5e6eb;
|
||
}
|
||
}
|
||
|
||
&.is-primary {
|
||
background: var(--app-primary-color);
|
||
color: #fff;
|
||
box-shadow: 0 8px 18px color-mix(in srgb, var(--app-primary-color) 28%, transparent);
|
||
|
||
&:active:not(:disabled) {
|
||
opacity: 0.92;
|
||
}
|
||
|
||
&:disabled {
|
||
opacity: 0.5;
|
||
box-shadow: none;
|
||
}
|
||
}
|
||
|
||
&:disabled {
|
||
cursor: not-allowed;
|
||
}
|
||
}
|
||
|
||
.h5-po__loading {
|
||
width: 16px;
|
||
height: 16px;
|
||
border: 2px solid rgba(255, 255, 255, 0.35);
|
||
border-top-color: #fff;
|
||
border-radius: 50%;
|
||
animation: h5-po-spin 0.7s linear infinite;
|
||
}
|
||
|
||
@keyframes h5-po-spin {
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
</style>
|