202 lines
11 KiB
Vue
202 lines
11 KiB
Vue
<script setup lang="ts">
|
|
import { computed, reactive, ref } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { ArrowLeft, Plus, Trash2 } from '@lucide/vue'
|
|
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
|
import { useAuthStore } from '@/modules/auth/public'
|
|
import { useUnsavedWorkRegistration } from '@/navigation/workspace-tabs/unsavedWork'
|
|
import { createIdempotencyKey } from '@/utils/idempotencyKey'
|
|
import { createDeliveryOrder, type DeliveryOrderFee, type DeliveryTransferPayload } from '../api/deliveryOrders'
|
|
|
|
type DeliveryCreateForm = Omit<DeliveryTransferPayload, 'idempotencyKey'>
|
|
|
|
const router = useRouter()
|
|
const auth = useAuthStore()
|
|
const formRef = ref<FormInstance>()
|
|
const submitting = ref(false)
|
|
const submitError = ref('')
|
|
const submitted = ref(false)
|
|
let pendingIdempotencyKey = ''
|
|
|
|
const form = reactive<DeliveryCreateForm>({
|
|
dispatchNo: '',
|
|
plannedDeliveryAt: '',
|
|
deliveryMethod: '',
|
|
pickupMethod: '',
|
|
loadingType: '',
|
|
waybillNo: '',
|
|
carrierName: '',
|
|
vehicleType: '',
|
|
vehicleSize: '',
|
|
driverName: '',
|
|
driverPhone: '',
|
|
vehiclePlate: '',
|
|
appointmentAccount: '',
|
|
sellerName: '',
|
|
remark: '',
|
|
fees: [],
|
|
})
|
|
|
|
const rules: FormRules<DeliveryCreateForm> = {
|
|
dispatchNo: [{ required: true, message: '请输入排车单号', trigger: 'blur' }],
|
|
plannedDeliveryAt: [{ required: true, message: '请选择计划派送时间', trigger: 'change' }],
|
|
deliveryMethod: [{ required: true, message: '请选择派送方式', trigger: 'change' }],
|
|
pickupMethod: [{ required: true, message: '请选择提货方式', trigger: 'change' }],
|
|
loadingType: [{ required: true, message: '请选择装载类型', trigger: 'change' }],
|
|
}
|
|
|
|
const dirty = computed(() => !submitted.value && Boolean(
|
|
form.dispatchNo || form.plannedDeliveryAt || form.deliveryMethod || form.pickupMethod || form.loadingType
|
|
|| form.waybillNo || form.carrierName || form.vehicleType || form.vehicleSize || form.driverName
|
|
|| form.driverPhone || form.vehiclePlate || form.appointmentAccount || form.sellerName || form.remark || form.fees.length,
|
|
))
|
|
|
|
useUnsavedWorkRegistration({
|
|
key: 'delivery-order-create-form',
|
|
routeKey: 'orders-delivery-create',
|
|
label: '创建派送单',
|
|
dirty,
|
|
})
|
|
|
|
function optional(value: string | undefined) {
|
|
const normalized = value?.trim()
|
|
return normalized || undefined
|
|
}
|
|
|
|
function addFee() {
|
|
form.fees.push({ feeType: '', currency: 'USD', amount: 0, remark: '' })
|
|
}
|
|
|
|
function removeFee(index: number) {
|
|
form.fees.splice(index, 1)
|
|
}
|
|
|
|
function validateFees() {
|
|
if (form.fees.length === 0) {
|
|
ElMessage.warning('请至少添加一条费用明细')
|
|
return false
|
|
}
|
|
const invalidIndex = form.fees.findIndex((fee) => !fee.feeType.trim() || !fee.currency.trim() || fee.amount <= 0)
|
|
if (invalidIndex < 0) return true
|
|
ElMessage.warning(`请完整填写第 ${invalidIndex + 1} 条费用的类型、币种和大于 0 的金额`)
|
|
return false
|
|
}
|
|
|
|
function requestPayload(): DeliveryTransferPayload {
|
|
return {
|
|
dispatchNo: form.dispatchNo.trim(),
|
|
idempotencyKey: pendingIdempotencyKey,
|
|
plannedDeliveryAt: form.plannedDeliveryAt,
|
|
deliveryMethod: form.deliveryMethod,
|
|
pickupMethod: form.pickupMethod,
|
|
loadingType: form.loadingType,
|
|
waybillNo: optional(form.waybillNo),
|
|
carrierName: optional(form.carrierName),
|
|
vehicleType: optional(form.vehicleType),
|
|
vehicleSize: optional(form.vehicleSize),
|
|
driverName: optional(form.driverName),
|
|
driverPhone: optional(form.driverPhone),
|
|
vehiclePlate: optional(form.vehiclePlate),
|
|
appointmentAccount: optional(form.appointmentAccount),
|
|
sellerName: optional(form.sellerName),
|
|
remark: optional(form.remark),
|
|
fees: form.fees.map((fee): DeliveryOrderFee => ({
|
|
feeType: fee.feeType.trim(),
|
|
currency: fee.currency.trim().toUpperCase(),
|
|
amount: fee.amount,
|
|
remark: optional(fee.remark ?? ''),
|
|
})),
|
|
}
|
|
}
|
|
|
|
async function submit() {
|
|
submitError.value = ''
|
|
const valid = await formRef.value?.validate().catch(() => false)
|
|
if (!valid || !validateFees()) return
|
|
if (!pendingIdempotencyKey) pendingIdempotencyKey = createIdempotencyKey()
|
|
submitting.value = true
|
|
try {
|
|
const created = await createDeliveryOrder(requestPayload())
|
|
submitted.value = true
|
|
pendingIdempotencyKey = ''
|
|
ElMessage.success('派送单创建成功')
|
|
if (auth.hasPermission('merchant:warehouse:delivery:list')) {
|
|
await router.replace({ name: 'orders-delivery-detail', params: { deliveryNo: created.deliveryNo } })
|
|
} else {
|
|
await router.replace('/')
|
|
}
|
|
} catch (error) {
|
|
submitError.value = error instanceof Error ? error.message : '派送单创建失败,请检查后重试'
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
|
|
async function cancel() {
|
|
if (dirty.value) {
|
|
try {
|
|
await ElMessageBox.confirm('当前派送单尚未提交,离开后填写内容将丢失。', '放弃创建?', { type: 'warning', confirmButtonText: '放弃', cancelButtonText: '继续填写' })
|
|
} catch (error) {
|
|
if (error === 'cancel' || error === 'close') return
|
|
throw error
|
|
}
|
|
}
|
|
submitted.value = true
|
|
await router.push({ name: 'orders-delivery' })
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<section class="delivery-page" aria-labelledby="delivery-create-title">
|
|
<button type="button" class="delivery-back" @click="cancel"><ArrowLeft :size="16" />返回派送单列表</button>
|
|
<div class="delivery-heading"><div><h2 id="delivery-create-title">创建派送单</h2><p>从已确认的排车单创建本企业海外仓派送任务。</p></div></div>
|
|
|
|
<div v-if="submitError" class="delivery-card delivery-state delivery-state--error" role="alert"><p>{{ submitError }}</p></div>
|
|
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="delivery-card delivery-form" @submit.prevent="submit">
|
|
<section>
|
|
<h3>基础信息</h3>
|
|
<div class="delivery-form-grid">
|
|
<el-form-item label="排车单号" prop="dispatchNo" required><el-input v-model="form.dispatchNo" maxlength="64" placeholder="请输入排车单号" /></el-form-item>
|
|
<el-form-item label="计划派送时间" prop="plannedDeliveryAt" required><el-date-picker v-model="form.plannedDeliveryAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择计划派送时间" /></el-form-item>
|
|
<el-form-item label="派送方式" prop="deliveryMethod" required><el-select v-model="form.deliveryMethod" placeholder="请选择派送方式"><el-option label="FBA自提" value="FBA_PICKUP" /><el-option label="客户自提" value="CUSTOMER_PICKUP" /><el-option label="卡派打托" value="TRUCK_PALLET" /><el-option label="卡派地板" value="TRUCK_FLOOR" /><el-option label="快递" value="EXPRESS" /><el-option label="外配" value="OUTSOURCED" /><el-option label="专车直送" value="DEDICATED_TRUCK" /><el-option label="零担派送" value="LTL" /></el-select></el-form-item>
|
|
<el-form-item label="提货方式" prop="pickupMethod" required><el-select v-model="form.pickupMethod" placeholder="请选择提货方式"><el-option label="一提一卸" value="ONE_PICKUP_ONE_DROP" /><el-option label="一提多卸" value="ONE_PICKUP_MULTI_DROP" /><el-option label="多提一卸" value="MULTI_PICKUP_ONE_DROP" /><el-option label="多提多卸" value="MULTI_PICKUP_MULTI_DROP" /><el-option label="仓库自提" value="WAREHOUSE_PICKUP" /></el-select></el-form-item>
|
|
<el-form-item label="装载类型" prop="loadingType" required><el-select v-model="form.loadingType" placeholder="请选择装载类型"><el-option label="托盘" value="PALLET" /><el-option label="地板" value="FLOOR" /><el-option label="混装" value="MIXED" /></el-select></el-form-item>
|
|
<el-form-item label="运单号"><el-input v-model="form.waybillNo" maxlength="64" placeholder="选填" /></el-form-item>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<h3>承运与车辆</h3>
|
|
<div class="delivery-form-grid">
|
|
<el-form-item label="承运商"><el-input v-model="form.carrierName" maxlength="128" placeholder="选填" /></el-form-item>
|
|
<el-form-item label="车型"><el-input v-model="form.vehicleType" maxlength="64" placeholder="选填" /></el-form-item>
|
|
<el-form-item label="车辆尺寸"><el-input v-model="form.vehicleSize" maxlength="64" placeholder="选填" /></el-form-item>
|
|
<el-form-item label="车牌号"><el-input v-model="form.vehiclePlate" maxlength="32" placeholder="选填" /></el-form-item>
|
|
<el-form-item label="司机姓名"><el-input v-model="form.driverName" maxlength="64" placeholder="选填" /></el-form-item>
|
|
<el-form-item label="司机电话"><el-input v-model="form.driverPhone" maxlength="32" placeholder="选填" /></el-form-item>
|
|
<el-form-item label="预约账号"><el-input v-model="form.appointmentAccount" maxlength="128" placeholder="选填" /></el-form-item>
|
|
<el-form-item label="卖家名称"><el-input v-model="form.sellerName" maxlength="128" placeholder="选填" /></el-form-item>
|
|
<el-form-item label="备注" class="delivery-span-3"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="选填" /></el-form-item>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<div class="delivery-fee-heading"><h3>费用明细</h3><el-button @click="addFee"><Plus :size="16" />添加费用</el-button></div>
|
|
<el-table v-if="form.fees.length" :data="form.fees" table-layout="fixed">
|
|
<el-table-column label="操作" width="88"><template #default="{ $index }"><el-button link type="danger" aria-label="删除费用" @click="removeFee($index)"><Trash2 :size="16" />删除</el-button></template></el-table-column>
|
|
<el-table-column label="费用类型" min-width="180"><template #default="{ row }"><el-input v-model="row.feeType" maxlength="64" placeholder="请输入费用类型" /></template></el-table-column>
|
|
<el-table-column label="币种" width="140"><template #default="{ row }"><el-input v-model="row.currency" maxlength="3" placeholder="USD" /></template></el-table-column>
|
|
<el-table-column label="金额" width="180"><template #default="{ row }"><el-input-number v-model="row.amount" :min="0" :precision="2" :step="1" controls-position="right" placeholder="请输入金额" /></template></el-table-column>
|
|
<el-table-column label="备注" min-width="220"><template #default="{ row }"><el-input v-model="row.remark" maxlength="200" placeholder="选填" /></template></el-table-column>
|
|
</el-table>
|
|
<div v-else class="delivery-fee-empty">请至少添加一条费用明细后再提交。</div>
|
|
</section>
|
|
|
|
<div class="delivery-form-actions"><el-button :disabled="submitting" @click="cancel">取消</el-button><el-button type="primary" native-type="submit" :loading="submitting">提交创建</el-button></div>
|
|
</el-form>
|
|
</section>
|
|
</template>
|
|
|
|
<style scoped src="./deliveryOrders.css"></style>
|