feat: update client navigation and first-leg orders
This commit is contained in:
+116
-108
@@ -2,7 +2,7 @@
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
||||
import { onBeforeRouteLeave, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormItemRule, type FormRules } from 'element-plus'
|
||||
import { AlertTriangle, Box, Building2, CheckCircle2, Eye, FilePlus2, MapPin, PackageCheck, Pencil, Plus, Send, Trash2 } from '@lucide/vue'
|
||||
import { AlertTriangle, Box, CheckCircle2, Eye, FilePlus2, MapPin, PackageCheck, Pencil, Plus, Send, Trash2 } from '@lucide/vue'
|
||||
import { FIRST_LEG_ORDER_STATUSES, FIRST_LEG_TRANSPORT_MODES, type FirstLegOrderStatus } from '@chongchong/shared'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/components/common/QueryToolbar.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/components/common/DataTable.vue'
|
||||
@@ -40,6 +40,12 @@ const queryFields: QueryField[] = [
|
||||
{ key: 'transportMode', type: 'select', placeholder: '全部运输方式', options: FIRST_LEG_TRANSPORT_MODES.map((item) => ({ label: item.label, value: item.code })), props: { 'aria-label': '运输方式' } },
|
||||
{ key: 'createdRange', type: 'daterange', placeholder: '订单创建日期', props: { 'aria-label': '订单创建日期' } },
|
||||
]
|
||||
const channelOptions = [
|
||||
{ code: 'SEA-US-GENERAL', name: '美线普货专线' },
|
||||
{ code: 'SEA-US-SENSITIVE', name: '美线敏感货专线' },
|
||||
{ code: 'SEA-US-EXPRESS', name: '美线快线' },
|
||||
{ code: 'SEA-US-FBA', name: '美线 FBA 直送' },
|
||||
]
|
||||
const orderColumns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 180, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'order', label: '订单', minWidth: 190, slotName: 'order' },
|
||||
@@ -133,7 +139,7 @@ function emptyDraft(): FirstLegOrderDraft {
|
||||
destinationWarehouseName: '',
|
||||
pickupMethod: 'SELF_EXPRESS',
|
||||
channelCode: 'SEA-US-FBA',
|
||||
channelName: '美国头程渠道',
|
||||
channelName: '美线 FBA 直送',
|
||||
taxMode: 'BUY_DECLARATION',
|
||||
customsInspection: false,
|
||||
destinationType: 'FBA',
|
||||
@@ -297,10 +303,13 @@ function selectParty(role: FirstLegPartyRole, id: string) {
|
||||
form.shipperPartyId = party.id
|
||||
form.shipperPartySnapshot = snapshot
|
||||
form.shipper = party.name
|
||||
form.origin = [snapshot.province, snapshot.city].filter(Boolean).join(' ') || snapshot.countryRegion || snapshot.name
|
||||
} else {
|
||||
form.consigneePartyId = party.id
|
||||
form.consigneePartySnapshot = snapshot
|
||||
form.consignee = party.name
|
||||
form.destination = [snapshot.province, snapshot.city].filter(Boolean).join(' ') || snapshot.countryRegion || snapshot.name
|
||||
if (/^[A-Za-z]{2}$/.test(snapshot.countryRegion)) form.destinationCountryCode = snapshot.countryRegion.toUpperCase()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,13 +318,24 @@ function clearParty(role: FirstLegPartyRole) {
|
||||
form.shipperPartyId = ''
|
||||
form.shipperPartySnapshot = null
|
||||
form.shipper = ''
|
||||
form.origin = ''
|
||||
} else {
|
||||
form.consigneePartyId = ''
|
||||
form.consigneePartySnapshot = null
|
||||
form.consignee = ''
|
||||
form.destination = ''
|
||||
}
|
||||
}
|
||||
|
||||
function selectChannel(code: string) {
|
||||
const channel = channelOptions.find((item) => item.code === code)
|
||||
form.channelName = channel?.name ?? ''
|
||||
}
|
||||
|
||||
function syncAmazonWarehouse(value: string) {
|
||||
if (form.destinationType === 'FBA') form.destinationWarehouseName = value.trim()
|
||||
}
|
||||
|
||||
function selectShipper(id: string) {
|
||||
selectParty('SHIPPER', id)
|
||||
}
|
||||
@@ -332,10 +352,11 @@ function clearConsignee() {
|
||||
clearParty('CONSIGNEE')
|
||||
}
|
||||
|
||||
async function loadSelectedWarehouse(id: string) {
|
||||
if (!id) return
|
||||
async function loadSelectedWarehouse(id: string | number | boolean | undefined) {
|
||||
const warehouseId = typeof id === 'string' ? id : typeof id === 'number' ? String(id) : ''
|
||||
if (!warehouseId) return
|
||||
try {
|
||||
await store.loadWarehouseDetail(id)
|
||||
await store.loadWarehouseDetail(warehouseId)
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '仓库地址与收货时间加载失败')
|
||||
}
|
||||
@@ -587,107 +608,85 @@ async function searchOrders(model: QueryModel) {
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<el-drawer v-model="editorOpen" class="first-leg-editor" :title="editingId ? '编辑订单草稿' : '创建头程订单'" size="760px" destroy-on-close :before-close="requestCloseEditor">
|
||||
<el-form ref="orderFormRef" :model="form" :rules="orderFormRules" label-position="top" class="order-form" scroll-to-error>
|
||||
<section class="order-form-section">
|
||||
<h3>运输信息</h3>
|
||||
<el-drawer v-model="editorOpen" class="first-leg-editor" :title="editingId ? '编辑订单草稿' : '创建头程订单'" size="1080px" destroy-on-close :before-close="requestCloseEditor">
|
||||
<el-form ref="orderFormRef" :model="form" :rules="orderFormRules" label-position="top" class="order-form prototype-order-form" scroll-to-error>
|
||||
<section class="order-form-section prototype-section">
|
||||
<div class="order-section-heading"><div><h3>下单信息</h3><p>选择送货方式、收货仓和运输渠道。</p></div></div>
|
||||
<div class="order-form-grid">
|
||||
<el-form-item label="运输方式" prop="transportMode" required><el-select v-model="form.transportMode"><el-option v-for="item in FIRST_LEG_TRANSPORT_MODES" :key="item.code" :label="item.label" :value="item.code" /></el-select></el-form-item>
|
||||
<el-form-item label="业务类型" prop="businessType" required><el-radio-group v-model="form.businessType"><el-radio-button value="EXPORT">出口</el-radio-button><el-radio-button value="IMPORT">进口</el-radio-button></el-radio-group></el-form-item>
|
||||
<el-form-item label="起运地" prop="origin" required><el-input v-model="form.origin" placeholder="例如:宁波" /></el-form-item>
|
||||
<el-form-item label="目的地" prop="destination" required><el-input v-model="form.destination" placeholder="例如:洛杉矶" /></el-form-item>
|
||||
<el-form-item label="平台收货仓" prop="receivingWarehouseId" required><el-select v-model="form.receivingWarehouseId" filterable placeholder="请选择送货仓库" @change="loadSelectedWarehouse"><el-option v-for="warehouse in availableWarehouses" :key="warehouse.id" :label="warehouse.city ? `${warehouse.name} · ${warehouse.city}` : warehouse.name" :value="warehouse.id" /></el-select></el-form-item>
|
||||
<el-form-item label="期望出发日期"><el-date-picker v-model="form.expectedDepartureDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择日期" /></el-form-item>
|
||||
<el-form-item label="客户参考号" prop="referenceNo" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('FBA 订单请输入客户参考号') : undefined"><el-input v-model="form.referenceNo" maxlength="100" :placeholder="form.destinationType === 'FBA' ? '请输入 FBA Reference ID' : '贵司内部单号(选填)'" /></el-form-item>
|
||||
<el-form-item label="优先级"><el-select v-model="form.priority"><el-option label="普通" value="NORMAL" /><el-option label="紧急" value="URGENT" /><el-option label="加急" value="EXPEDITED" /></el-select></el-form-item>
|
||||
<el-form-item label="目的国代码" prop="destinationCountryCode" required><el-input v-model="form.destinationCountryCode" maxlength="2" placeholder="例如:US" /></el-form-item>
|
||||
<el-form-item label="目的仓名称" prop="destinationWarehouseName" required><el-input v-model="form.destinationWarehouseName" maxlength="128" placeholder="例如:ONT8 或洛杉矶海外仓" /></el-form-item>
|
||||
<el-form-item label="渠道编码" prop="channelCode" required><el-input v-model="form.channelCode" maxlength="64" placeholder="例如:SEA-US-FBA" /></el-form-item>
|
||||
<el-form-item label="渠道名称" prop="channelName" required><el-input v-model="form.channelName" maxlength="128" placeholder="例如:美国海运 FBA 渠道" /></el-form-item>
|
||||
<el-form-item label="提货方式" prop="pickupMethod" required><el-select v-model="form.pickupMethod"><el-option label="自送快递" value="SELF_EXPRESS" /><el-option label="上门提货" value="PICKUP" /><el-option label="自送卡车" value="SELF_TRUCK" /></el-select></el-form-item>
|
||||
<el-form-item label="报关模式" prop="taxMode" required><el-select v-model="form.taxMode"><el-option label="买单报关" value="BUY_DECLARATION" /><el-option label="退税报关" value="TAX_REFUND" /></el-select></el-form-item>
|
||||
<el-form-item label="目的仓类型" prop="destinationType" required><el-select v-model="form.destinationType"><el-option label="FBA" value="FBA" /><el-option label="海外仓" value="OVERSEAS" /><el-option label="私人地址" value="PRIVATE" /></el-select></el-form-item>
|
||||
<el-form-item label="贸易术语"><el-input v-model="form.tradeTerm" maxlength="16" placeholder="例如:FOB" /></el-form-item>
|
||||
<el-form-item v-if="form.destinationType === 'FBA'" label="Amazon 仓库编码" prop="amazonWarehouseCode" :rules="requiredTextRules('请输入 Amazon 仓库编码')" required><el-input v-model="form.amazonWarehouseCode" maxlength="32" placeholder="例如:ONT8" /></el-form-item>
|
||||
<el-form-item v-if="form.destinationType === 'FBA'" label="Shipment ID" prop="shipmentId" :rules="requiredTextRules('请输入 Shipment ID')" required><el-input v-model="form.shipmentId" maxlength="64" placeholder="例如:FBA15ABC123" /></el-form-item>
|
||||
<el-form-item label="取货方式" prop="pickupMethod" required class="form-span-full"><el-radio-group v-model="form.pickupMethod" class="segmented-options"><el-radio-button value="SELF_EXPRESS">自送货 · 快递</el-radio-button><el-radio-button value="PICKUP">我司提货</el-radio-button><el-radio-button value="SELF_TRUCK">自送货 · 卡车</el-radio-button></el-radio-group></el-form-item>
|
||||
<el-form-item label="平台收货仓" prop="receivingWarehouseId" required class="form-span-full"><el-radio-group v-model="form.receivingWarehouseId" class="warehouse-options" @change="loadSelectedWarehouse"><el-radio-button v-for="warehouse in availableWarehouses" :key="warehouse.id" :value="warehouse.id">{{ warehouse.city ? `${warehouse.name} · ${warehouse.city}` : warehouse.name }}</el-radio-button></el-radio-group></el-form-item>
|
||||
<el-form-item label="渠道" prop="channelCode" required><el-select v-model="form.channelCode" placeholder="请选择运输渠道" @change="selectChannel"><el-option v-if="form.channelCode && !channelOptions.some((channel) => channel.code === form.channelCode)" :label="form.channelName || form.channelCode" :value="form.channelCode" /><el-option v-for="channel in channelOptions" :key="channel.code" :label="channel.name" :value="channel.code" /></el-select></el-form-item>
|
||||
<el-form-item label="买单 / 退税" prop="taxMode" required><el-radio-group v-model="form.taxMode"><el-radio value="BUY_DECLARATION">买单报关</el-radio><el-radio value="TAX_REFUND">退税报关</el-radio></el-radio-group></el-form-item>
|
||||
<el-form-item label="运输方式" prop="transportMode" required><el-radio-group v-model="form.transportMode"><el-radio v-for="item in FIRST_LEG_TRANSPORT_MODES" :key="item.code" :value="item.code">{{ item.label }}</el-radio></el-radio-group></el-form-item>
|
||||
<el-form-item label="是否商检" prop="customsInspection" required><el-radio-group v-model="form.customsInspection"><el-radio :value="true">是</el-radio><el-radio :value="false">否</el-radio></el-radio-group></el-form-item>
|
||||
<el-form-item label="期望出货日期"><el-date-picker v-model="form.expectedDepartureDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择期望出货日期" /></el-form-item>
|
||||
<el-form-item label="订单备注"><el-input v-model="form.customerRemark" maxlength="1000" placeholder="选填,例如:入仓前请电话联系" /></el-form-item>
|
||||
</div>
|
||||
<div v-if="selectedWarehouse" class="selected-warehouse"><MapPin :size="17" /><div><strong>{{ selectedWarehouse.name }}</strong><span>{{ selectedWarehouse.address ? `${selectedWarehouse.province}${selectedWarehouse.city} · ${selectedWarehouse.address}` : '仓库地址详情暂不可用' }}</span><small>{{ selectedWarehouse.businessHours ? `收货时间:${selectedWarehouse.businessHours}` : '收货时间暂不可用' }}</small></div></div>
|
||||
</section>
|
||||
|
||||
<section class="order-form-section">
|
||||
<div class="order-section-heading"><div><h3>收发货单位</h3><p>仅可选择平台已启用的往来单位;下方联系人和地址调整只影响本订单快照。</p></div></div>
|
||||
<div class="party-editor-grid">
|
||||
<article class="party-editor">
|
||||
<header><span><Building2 :size="17" /><strong>发货单位</strong></span><small>必填</small></header>
|
||||
<el-form-item label="选择发货单位" prop="shipperPartyId" required>
|
||||
<el-select v-model="form.shipperPartyId" filterable clearable placeholder="请选择已启用的发货单位" @change="selectShipper" @clear="clearShipper">
|
||||
<el-option v-if="form.shipperPartySnapshot && !availableShippers.some((party) => party.id === form.shipperPartyId)" :label="`${form.shipperPartySnapshot.name} · 历史快照`" :value="form.shipperPartyId" disabled />
|
||||
<el-option v-for="party in availableShippers" :key="party.id" :label="`${party.name} · ${party.code}`" :value="party.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div v-if="form.shipperPartySnapshot" class="party-snapshot-fields">
|
||||
<div class="party-name"><strong>{{ form.shipperPartySnapshot.name }}</strong><span>{{ form.shipperPartySnapshot.code }}</span></div>
|
||||
<el-form-item label="联系人" prop="shipperPartySnapshot.contactName" required :rules="requiredTextRules('请输入发货联系人姓名')"><el-input v-model="form.shipperPartySnapshot.contactName" maxlength="50" placeholder="请输入发货联系人姓名" /></el-form-item>
|
||||
<el-form-item label="联系电话" prop="shipperPartySnapshot.phone" required :rules="requiredTextRules('请输入发货联系电话')"><el-input v-model="form.shipperPartySnapshot.phone" maxlength="30" placeholder="请输入发货联系电话" /></el-form-item>
|
||||
<el-form-item label="联系邮箱"><el-input v-model="form.shipperPartySnapshot.email" maxlength="100" placeholder="例如:name@example.com" /></el-form-item>
|
||||
<el-form-item label="本单发货地址" prop="shipperPartySnapshot.address" required :rules="requiredTextRules('请输入本单实际发货地址')"><el-input v-model="form.shipperPartySnapshot.address" maxlength="300" placeholder="请输入本单实际发货地址" /></el-form-item>
|
||||
<section class="order-form-section prototype-section destination-section">
|
||||
<div class="order-section-heading"><div><h3>发货与目的地</h3><p>单位信息会保存为本订单快照,后续主数据变更不会影响本单。</p></div></div>
|
||||
<el-form-item label="目的仓类型" prop="destinationType" required><el-radio-group v-model="form.destinationType" class="segmented-options"><el-radio-button value="FBA">FBA 仓</el-radio-button><el-radio-button value="OVERSEAS">海外仓</el-radio-button><el-radio-button value="PRIVATE">私人仓</el-radio-button></el-radio-group></el-form-item>
|
||||
<div class="party-select-grid">
|
||||
<div class="party-panel">
|
||||
<h4>发货信息</h4>
|
||||
<el-alert v-if="availableShippers.length === 0" title="暂无可用发货单位" description="请先在平台往来单位中启用“发货单位”类型。" type="warning" :closable="false" show-icon />
|
||||
<el-form-item label="发货单位" prop="shipperPartyId" required><el-select v-model="form.shipperPartyId" filterable clearable placeholder="请选择发货单位" @change="selectShipper" @clear="clearShipper"><el-option v-if="form.shipperPartySnapshot && !availableShippers.some((party) => party.id === form.shipperPartyId)" :label="`${form.shipperPartySnapshot.name} · 历史快照`" :value="form.shipperPartyId" disabled /><el-option v-for="party in availableShippers" :key="party.id" :label="`${party.name} · ${party.code}`" :value="party.id" /></el-select></el-form-item>
|
||||
<div v-if="form.shipperPartySnapshot" class="party-contact-grid">
|
||||
<el-form-item label="联系人" prop="shipperPartySnapshot.contactName" required :rules="requiredTextRules('请输入发货联系人姓名')"><el-input v-model="form.shipperPartySnapshot.contactName" maxlength="50" placeholder="请输入联系人" /></el-form-item>
|
||||
<el-form-item label="联系电话" prop="shipperPartySnapshot.phone" required :rules="requiredTextRules('请输入发货联系电话')"><el-input v-model="form.shipperPartySnapshot.phone" maxlength="30" placeholder="请输入联系电话" /></el-form-item>
|
||||
<el-form-item label="发货地址" prop="shipperPartySnapshot.address" required :rules="requiredTextRules('请输入本单实际发货地址')" class="form-span-full"><el-input v-model="form.shipperPartySnapshot.address" maxlength="300" placeholder="请输入本单实际发货地址" /></el-form-item>
|
||||
</div>
|
||||
<div v-else class="party-empty">选择后可核对本单联系人和发货地址</div>
|
||||
</article>
|
||||
|
||||
<article class="party-editor">
|
||||
<header><span><Building2 :size="17" /><strong>收货单位</strong></span><small>必填</small></header>
|
||||
<el-form-item label="选择收货单位" prop="consigneePartyId" required>
|
||||
<el-select v-model="form.consigneePartyId" filterable clearable placeholder="请选择已启用的收货单位" @change="selectConsignee" @clear="clearConsignee">
|
||||
<el-option v-if="form.consigneePartySnapshot && !availableConsignees.some((party) => party.id === form.consigneePartyId)" :label="`${form.consigneePartySnapshot.name} · 历史快照`" :value="form.consigneePartyId" disabled />
|
||||
<el-option v-for="party in availableConsignees" :key="party.id" :label="`${party.name} · ${party.code}`" :value="party.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div v-if="form.consigneePartySnapshot" class="party-snapshot-fields">
|
||||
<div class="party-name"><strong>{{ form.consigneePartySnapshot.name }}</strong><span>{{ form.consigneePartySnapshot.code }}</span></div>
|
||||
<el-form-item label="联系人" prop="consigneePartySnapshot.contactName" required :rules="requiredTextRules('请输入收货联系人姓名')"><el-input v-model="form.consigneePartySnapshot.contactName" maxlength="50" placeholder="请输入收货联系人姓名" /></el-form-item>
|
||||
<el-form-item label="联系电话" prop="consigneePartySnapshot.phone" required :rules="requiredTextRules('请输入收货联系电话')"><el-input v-model="form.consigneePartySnapshot.phone" maxlength="30" placeholder="请输入收货联系电话" /></el-form-item>
|
||||
<el-form-item label="联系邮箱"><el-input v-model="form.consigneePartySnapshot.email" maxlength="100" placeholder="例如:name@example.com" /></el-form-item>
|
||||
<el-form-item label="本单收货地址" prop="consigneePartySnapshot.address" required :rules="requiredTextRules('请输入本单实际收货地址')"><el-input v-model="form.consigneePartySnapshot.address" maxlength="300" placeholder="请输入本单实际收货地址" /></el-form-item>
|
||||
</div>
|
||||
<div class="party-panel">
|
||||
<h4>目的地信息</h4>
|
||||
<el-alert v-if="availableConsignees.length === 0" title="暂无可用收货单位" description="请先在平台往来单位中启用“收货单位”类型。" type="warning" :closable="false" show-icon />
|
||||
<el-form-item label="收货单位" prop="consigneePartyId" required><el-select v-model="form.consigneePartyId" filterable clearable placeholder="请选择收货单位" @change="selectConsignee" @clear="clearConsignee"><el-option v-if="form.consigneePartySnapshot && !availableConsignees.some((party) => party.id === form.consigneePartyId)" :label="`${form.consigneePartySnapshot.name} · 历史快照`" :value="form.consigneePartyId" disabled /><el-option v-for="party in availableConsignees" :key="party.id" :label="`${party.name} · ${party.code}`" :value="party.id" /></el-select></el-form-item>
|
||||
<div v-if="form.consigneePartySnapshot" class="party-contact-grid">
|
||||
<el-form-item label="收件人" prop="consigneePartySnapshot.contactName" required :rules="requiredTextRules('请输入收货联系人姓名')"><el-input v-model="form.consigneePartySnapshot.contactName" maxlength="50" placeholder="请输入收件人姓名" /></el-form-item>
|
||||
<el-form-item label="联系电话" prop="consigneePartySnapshot.phone" required :rules="requiredTextRules('请输入收货联系电话')"><el-input v-model="form.consigneePartySnapshot.phone" maxlength="30" placeholder="请输入收件人电话" /></el-form-item>
|
||||
<el-form-item label="收件地址" prop="consigneePartySnapshot.address" required :rules="requiredTextRules('请输入本单实际收货地址')" class="form-span-full"><el-input v-model="form.consigneePartySnapshot.address" maxlength="300" placeholder="请输入收件详细地址" /></el-form-item>
|
||||
</div>
|
||||
<div v-else class="party-empty">选择后可核对本单联系人和收货地址</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<div class="destination-fields">
|
||||
<el-form-item label="目的国代码" prop="destinationCountryCode" required><el-input v-model="form.destinationCountryCode" maxlength="2" placeholder="例如:US" /></el-form-item>
|
||||
<el-form-item v-if="form.destinationType === 'FBA'" label="亚马逊仓代码" prop="amazonWarehouseCode" required :rules="requiredTextRules('请输入 Amazon 仓库编码')"><el-input v-model="form.amazonWarehouseCode" maxlength="32" placeholder="例如:ONT8" @input="syncAmazonWarehouse" /></el-form-item>
|
||||
<el-form-item v-else label="目的仓名称" prop="destinationWarehouseName" required><el-input v-model="form.destinationWarehouseName" maxlength="128" placeholder="请输入海外仓或私人仓名称" /></el-form-item>
|
||||
<el-form-item v-if="form.destinationType === 'FBA'" label="货件编号" prop="shipmentId" required :rules="requiredTextRules('请输入 Shipment ID')"><el-input v-model="form.shipmentId" maxlength="64" placeholder="Shipment ID" /></el-form-item>
|
||||
<el-form-item label="跟踪编号" prop="referenceNo" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('FBA 订单请输入 Reference ID') : undefined"><el-input v-model="form.referenceNo" maxlength="100" :placeholder="form.destinationType === 'FBA' ? 'Reference ID' : '选填,贵司内部跟踪编号'" /></el-form-item>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="order-form-section">
|
||||
<div class="order-section-heading"><div><h3>货物明细</h3><p>总件数、重量和体积将在提交时由服务端汇总。</p></div><el-button @click="addCargoItem"><Plus :size="16" />添加货物</el-button></div>
|
||||
<article v-for="(item, index) in form.items" :key="item.id" class="cargo-editor-card">
|
||||
<section class="order-form-section prototype-section cargo-section">
|
||||
<div class="order-section-heading"><div><h3>货物明细</h3><p>先填写常用字段,申报和整柜信息可按需展开。</p></div><el-button @click="addCargoItem"><Plus :size="16" />添加货物</el-button></div>
|
||||
<article v-for="(item, index) in form.items" :key="item.id" class="cargo-entry">
|
||||
<header><strong>货物 {{ index + 1 }}</strong><el-button link type="danger" :aria-label="`删除货物 ${index + 1}`" @click="removeCargoItem(index)"><Trash2 :size="16" />删除</el-button></header>
|
||||
<div class="cargo-form-grid">
|
||||
<div class="cargo-primary-grid">
|
||||
<el-form-item label="SKU" :prop="`items.${index}.sku`" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('FBA 货物请输入客户 SKU') : undefined"><el-input v-model="item.sku" placeholder="SKU NO." /></el-form-item>
|
||||
<el-form-item label="中文品名" :prop="`items.${index}.cargoNameCn`" required :rules="requiredTextRules('请输入中文品名')"><el-input v-model="item.cargoNameCn" placeholder="例如:蓝牙音箱" /></el-form-item>
|
||||
<el-form-item label="英文品名"><el-input v-model="item.cargoNameEn" placeholder="例如:Bluetooth speakers" /></el-form-item>
|
||||
<el-form-item label="客户 SKU" :prop="`items.${index}.sku`" :required="form.destinationType === 'FBA'" :rules="form.destinationType === 'FBA' ? requiredTextRules('FBA 货物请输入客户 SKU') : undefined"><el-input v-model="item.sku" placeholder="例如:SKU-001" /></el-form-item>
|
||||
<el-form-item v-if="form.destinationType === 'FBA'" label="UPC" :prop="`items.${index}.upc`" required :rules="requiredTextRules('FBA 货物请输入 UPC')"><el-input v-model="item.upc" maxlength="32" placeholder="例如:012345678905" /></el-form-item>
|
||||
<el-form-item label="英文品名"><el-input v-model="item.cargoNameEn" placeholder="Bluetooth speakers" /></el-form-item>
|
||||
<el-form-item v-if="form.destinationType === 'FBA'" label="UPC" :prop="`items.${index}.upc`" required :rules="requiredTextRules('FBA 货物请输入 UPC')"><el-input v-model="item.upc" maxlength="32" placeholder="UPC NO." /></el-form-item>
|
||||
<el-form-item label="包装类型" :prop="`items.${index}.packageType`" required :rules="requiredSelectRules('请选择包装类型')"><el-select v-model="item.packageType"><el-option label="纸箱" value="CARTON" /><el-option label="托盘" value="PALLET" /><el-option label="木箱" value="WOODEN_CASE" /></el-select></el-form-item>
|
||||
<el-form-item label="HS Code"><el-input v-model="item.hsCode" maxlength="20" placeholder="例如:8518210000" /></el-form-item>
|
||||
<el-form-item label="件数" :prop="`items.${index}.pieces`" required :rules="positiveNumberRules('件数必须为大于 0 的整数', true)"><el-input-number v-model="item.pieces" :min="1" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="总数量" :prop="`items.${index}.pieces`" required :rules="positiveNumberRules('件数必须为大于 0 的整数', true)"><el-input-number v-model="item.pieces" :min="1" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="箱数"><el-input-number v-model="item.packageCount" :min="0" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="毛重 (kg)" :prop="`items.${index}.grossWeightKg`" required :rules="positiveNumberRules('毛重必须大于 0')"><el-input-number v-model="item.grossWeightKg" :min="0" :precision="2" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="体积 (m³)" :prop="`items.${index}.volumeCbm`" required :rules="positiveNumberRules('体积必须大于 0')"><el-input-number v-model="item.volumeCbm" :min="0" :precision="3" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="总毛重 kg" :prop="`items.${index}.grossWeightKg`" required :rules="positiveNumberRules('毛重必须大于 0')"><el-input-number v-model="item.grossWeightKg" :min="0" :precision="2" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="总体积 m³" :prop="`items.${index}.volumeCbm`" required :rules="positiveNumberRules('体积必须大于 0')"><el-input-number v-model="item.volumeCbm" :min="0" :precision="3" controls-position="right" /></el-form-item>
|
||||
</div>
|
||||
<el-collapse class="cargo-more"><el-collapse-item title="更多申报信息"><div class="cargo-secondary-grid">
|
||||
<el-form-item label="HS Code"><el-input v-model="item.hsCode" maxlength="20" placeholder="例如:8518210000" /></el-form-item>
|
||||
<el-form-item label="申报货值"><el-input-number v-model="item.declaredValue" :min="0" :precision="2" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="币种"><el-input v-model="item.currency" maxlength="3" placeholder="例如:USD" /></el-form-item>
|
||||
<el-form-item label="危险品"><el-switch v-model="item.dangerousGoods" /></el-form-item>
|
||||
<template v-if="form.transportMode === 'FCL'">
|
||||
<el-form-item label="箱型" :prop="`items.${index}.containerType`" required :rules="requiredSelectRules('请选择箱型')"><el-select v-model="item.containerType"><el-option v-for="type in ['20GP', '40GP', '40HQ']" :key="type" :label="type" :value="type" /></el-select></el-form-item>
|
||||
<el-form-item label="柜量" :prop="`items.${index}.containerCount`" required :rules="positiveNumberRules('柜量必须为大于 0 的整数', true)"><el-input-number v-model="item.containerCount" :min="0" controls-position="right" /></el-form-item>
|
||||
</template>
|
||||
<el-form-item label="货物备注" class="cargo-remark"><el-input v-model="item.remark" maxlength="300" placeholder="选填,例如:注意防潮" /></el-form-item>
|
||||
</div>
|
||||
<template v-if="form.transportMode === 'FCL'"><el-form-item label="箱型" :prop="`items.${index}.containerType`" required :rules="requiredSelectRules('请选择箱型')"><el-select v-model="item.containerType"><el-option v-for="type in ['20GP', '40GP', '40HQ']" :key="type" :label="type" :value="type" /></el-select></el-form-item><el-form-item label="柜量" :prop="`items.${index}.containerCount`" required :rules="positiveNumberRules('柜量必须为大于 0 的整数', true)"><el-input-number v-model="item.containerCount" :min="0" controls-position="right" /></el-form-item></template>
|
||||
<el-form-item label="货物备注" class="form-span-full"><el-input v-model="item.remark" maxlength="300" placeholder="选填,例如:注意防潮" /></el-form-item>
|
||||
</div></el-collapse-item></el-collapse>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="order-form-section">
|
||||
<h3>订单备注</h3>
|
||||
<el-form-item label="客户备注"><el-input v-model="form.customerRemark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="平台工作人员可以看到该备注" /></el-form-item>
|
||||
<p class="cargo-tip">FBA 货物必须填写 SKU、UPC、货件编号和跟踪编号;提交前请核对重量与体积。</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" />{{ companyApproved ? '提交审核' : '提交前需完成资料审核' }}</el-button></div></template>
|
||||
<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" />{{ companyApproved ? '提交订单' : '提交前需完成资料审核' }}</el-button></div></template>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer v-model="detailOpen" class="first-leg-detail" title="订单详情" size="680px">
|
||||
@@ -761,33 +760,38 @@ async function searchOrders(model: QueryModel) {
|
||||
.order-form-section + .order-form-section { margin-top: 24px; padding-top: 22px; border-top: 1px solid var(--border); }
|
||||
.order-form-section h3 { margin: 0 0 16px; color: var(--navy); font-size: 16px; }
|
||||
.order-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 18px; }
|
||||
.order-form-grid > *, .cargo-form-grid > * { min-width: 0; }
|
||||
.order-form-grid :deep(.el-select), .order-form-grid :deep(.el-date-editor), .cargo-form-grid :deep(.el-select), .cargo-form-grid :deep(.el-input-number) { width: 100%; }
|
||||
.order-form-grid > *, .destination-fields > *, .party-contact-grid > *, .cargo-primary-grid > *, .cargo-secondary-grid > * { min-width: 0; }
|
||||
.order-form :deep(.el-select), .order-form :deep(.el-date-editor), .order-form :deep(.el-input-number) { width: 100%; }
|
||||
.form-span-full { grid-column: 1 / -1; }
|
||||
.prototype-section { padding: 2px 2px 0; }
|
||||
.segmented-options { display: flex; width: min(100%, 620px); }
|
||||
.segmented-options :deep(.el-radio-button) { flex: 1; }
|
||||
.segmented-options :deep(.el-radio-button__inner) { width: 100%; min-height: 42px; padding: 11px 16px; }
|
||||
.warehouse-options { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
.warehouse-options :deep(.el-radio-button__inner) { min-height: 40px; border: 1px solid var(--border); border-radius: 6px !important; box-shadow: none !important; }
|
||||
.warehouse-options :deep(.el-radio-button.is-active .el-radio-button__inner) { border-color: var(--primary); }
|
||||
.selected-warehouse { display: flex; gap: 10px; margin-top: -2px; padding: 12px 14px; border: 1px solid #cfe1dd; background: #f3f8f7; color: var(--primary); }
|
||||
.selected-warehouse svg { flex: 0 0 17px; margin-top: 2px; }
|
||||
.selected-warehouse div { display: flex; min-width: 0; flex-direction: column; gap: 4px; }
|
||||
.selected-warehouse span, .selected-warehouse small { color: var(--muted); font-size: 11px; line-height: 1.5; }
|
||||
.party-editor-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
|
||||
.party-editor { min-width: 0; padding: 16px; border: 1px solid var(--border); background: #f9fbfb; }
|
||||
.party-editor > header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
|
||||
.party-editor > header span { display: flex; min-width: 0; align-items: center; gap: 8px; color: var(--navy); }
|
||||
.party-editor > header svg { flex: 0 0 17px; color: var(--primary); }
|
||||
.party-editor > header small { color: var(--muted); font-size: 11px; }
|
||||
.party-editor :deep(.el-select) { width: 100%; }
|
||||
.party-snapshot-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
|
||||
.party-name { display: flex; min-width: 0; grid-column: 1 / -1; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
|
||||
.party-name strong { min-width: 0; overflow-wrap: anywhere; color: var(--navy); font-size: 13px; }
|
||||
.party-name span { flex: 0 0 auto; color: var(--muted); font-size: 10px; }
|
||||
.party-snapshot-fields .el-form-item:last-child { grid-column: 1 / -1; }
|
||||
.party-empty { display: grid; min-height: 150px; place-items: center; border-top: 1px solid var(--border); color: var(--muted); font-size: 12px; text-align: center; }
|
||||
.order-section-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
|
||||
.order-section-heading h3 { margin-bottom: 5px; }
|
||||
.order-section-heading p { margin: 0; color: var(--muted); font-size: 11px; }
|
||||
.cargo-editor-card { padding: 16px; border: 1px solid var(--border); background: #f9fbfb; }
|
||||
.cargo-editor-card + .cargo-editor-card { margin-top: 12px; }
|
||||
.cargo-editor-card header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
|
||||
.cargo-form-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: 14px; }
|
||||
.cargo-remark { grid-column: 1 / -1; }
|
||||
.party-select-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
|
||||
.party-panel { min-width: 0; padding: 16px; border: 1px solid var(--border); background: #fbfcfc; }
|
||||
.party-panel h4 { margin: 0 0 14px; color: var(--navy); font-size: 14px; }
|
||||
.party-panel :deep(.el-alert) { margin-bottom: 14px; }
|
||||
.party-contact-grid, .destination-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 12px; }
|
||||
.destination-fields { margin-top: 16px; }
|
||||
.cargo-entry { padding: 16px; border: 1px solid var(--border); background: #fbfcfc; }
|
||||
.cargo-entry + .cargo-entry { margin-top: 12px; }
|
||||
.cargo-entry > header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
|
||||
.cargo-primary-grid { display: grid; grid-template-columns: repeat(5, minmax(130px, 1fr)); column-gap: 12px; }
|
||||
.cargo-secondary-grid { display: grid; grid-template-columns: repeat(4, minmax(130px, 1fr)); column-gap: 12px; padding-top: 6px; }
|
||||
.cargo-more { border-top: 1px solid var(--border); border-bottom: 0; }
|
||||
.cargo-more :deep(.el-collapse-item__header), .cargo-more :deep(.el-collapse-item__wrap) { background: transparent; }
|
||||
.cargo-more :deep(.el-collapse-item__content) { padding-bottom: 0; }
|
||||
.cargo-tip { margin: 12px 0 0; color: var(--muted); font-size: 11px; line-height: 1.55; }
|
||||
.order-detail-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding-bottom: 20px; border-bottom: 1px solid var(--border); }
|
||||
.order-detail-heading span { color: var(--muted); font-size: 11px; }
|
||||
.order-detail-heading h2 { margin: 6px 0; font-size: 21px; }
|
||||
@@ -841,7 +845,12 @@ async function searchOrders(model: QueryModel) {
|
||||
.order-table-toolbar .el-button { width: 100%; min-height: 40px; }
|
||||
.order-eligibility { flex-wrap: wrap; }
|
||||
.order-eligibility .el-button { width: 100%; min-height: 44px; }
|
||||
.order-form-grid, .cargo-form-grid, .order-detail-grid, .party-editor-grid, .party-snapshot-fields { grid-template-columns: 1fr; }
|
||||
.order-form-grid, .order-detail-grid, .party-select-grid, .party-contact-grid, .destination-fields, .cargo-primary-grid, .cargo-secondary-grid { grid-template-columns: 1fr; }
|
||||
.segmented-options { width: 100%; align-items: stretch; flex-direction: column; }
|
||||
.segmented-options :deep(.el-radio-button__inner) { border: 1px solid var(--border); border-radius: 6px !important; box-shadow: none !important; }
|
||||
.warehouse-options { display: grid; grid-template-columns: 1fr; }
|
||||
.warehouse-options :deep(.el-radio-button), .warehouse-options :deep(.el-radio-button__inner) { width: 100%; }
|
||||
.form-span-full { grid-column: auto; }
|
||||
.editor-actions :deep(.el-button) { min-height: 44px; }
|
||||
.desktop-order-table { display: none; }
|
||||
.order-load-state { min-height: 240px; grid-template-columns: 1fr; justify-items: start; }
|
||||
@@ -861,7 +870,6 @@ async function searchOrders(model: QueryModel) {
|
||||
.mobile-order-card footer { align-items: flex-end; padding-top: 10px; border-top: 1px solid var(--border); }
|
||||
.mobile-order-card footer > div { display: flex; }
|
||||
.order-detail-grid div:nth-child(odd) { border-right: 0; }
|
||||
.party-name, .party-snapshot-fields .el-form-item:last-child { grid-column: auto; }
|
||||
.express-information { grid-template-columns: auto 1fr; }
|
||||
.express-information > div:last-child { grid-column: 2; }
|
||||
.cargo-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
|
||||
Reference in New Issue
Block a user