merge: preserve remote client updates and truck demand contract
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { Package, Truck } from '@lucide/vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import DataTable, { type DataTableColumn } from '@/components/common/DataTable.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/components/common/QueryToolbar.vue'
|
||||
import type { TableAction } from '@/components/common/tableActions'
|
||||
import { useAuthStore } from '@/modules/auth/public'
|
||||
import { getMerchantWarehouses, type MerchantWarehouse } from '@/modules/first-leg/public'
|
||||
import {
|
||||
createTransport,
|
||||
getBusinessOrder,
|
||||
@@ -58,6 +60,9 @@ interface BusinessForm {
|
||||
contactInfo: string
|
||||
requestedDate: string
|
||||
remark: string
|
||||
cargoName: string
|
||||
deliveryDate: string
|
||||
palletCount: number
|
||||
}
|
||||
|
||||
const configuration = computed(() => ({
|
||||
@@ -102,6 +107,10 @@ const fleetArrangement = computed(() => detail.value ? (detail.value as Transpor
|
||||
const formRef = ref<FormInstance>()
|
||||
const query = reactive({ page: 1, size: 20, orderNo: '', routeKeyword: '', status: '' })
|
||||
const total = ref(0)
|
||||
const overseasWarehouses = ref<MerchantWarehouse[]>([])
|
||||
const overseasWarehousesLoading = ref(false)
|
||||
const overseasWarehousesError = ref('')
|
||||
const isSelfDelivery = computed(() => props.kind === 'looseboard' && form.transportMode === 'SHARED_TRUCK')
|
||||
|
||||
const queryModel = computed<QueryModel>({
|
||||
get: () => ({
|
||||
@@ -201,11 +210,30 @@ function emptyForm(): BusinessForm {
|
||||
transportMode: 'SHARED_TRUCK', palletRequired: 'NO', transportType: 'PORT_PICKUP', origin: '', originAddress: '',
|
||||
destination: '', destinationAddress: '', containerType: DEFAULT_CONTAINER_TYPE, packageCount: 0,
|
||||
totalWeightKg: 0, totalVolumeCbm: 0, currency: 'USD', contactName: '', contactInfo: '', requestedDate: '',
|
||||
remark: '', vehicleType: '', originContactName: '', originContactPhone: '', destinationContactName: '', destinationContactPhone: '', pickupAt: '', deliveryAt: '',
|
||||
remark: '', cargoName: '', deliveryDate: '', palletCount: 0, vehicleType: '', originContactName: '', originContactPhone: '', destinationContactName: '', destinationContactPhone: '', pickupAt: '', deliveryAt: '',
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() { Object.assign(form, emptyForm()); submitKey.value = ''; formRef.value?.clearValidate() }
|
||||
function warehouseLabel(warehouse: MerchantWarehouse) { return `${warehouse.warehouseName} · ${warehouse.warehouseCode}` }
|
||||
function selectOriginMode() {
|
||||
form.origin = ''
|
||||
form.originAddress = ''
|
||||
formRef.value?.clearValidate('origin')
|
||||
}
|
||||
async function loadOverseasWarehouses() {
|
||||
if (props.kind !== 'looseboard') return
|
||||
overseasWarehousesLoading.value = true
|
||||
overseasWarehousesError.value = ''
|
||||
try {
|
||||
const result = await getMerchantWarehouses({ page: 1, size: 200, warehouseType: 'OVERSEAS' })
|
||||
overseasWarehouses.value = result.records.filter(item => item.status === 'ACTIVE' && ['OVERSEAS', 'OVERSEAS_PARTNER'].includes(item.warehouseType) && item.countryCode === 'US')
|
||||
if (!overseasWarehouses.value.length) overseasWarehousesError.value = '暂无可用的海外仓'
|
||||
} catch (cause) {
|
||||
overseasWarehouses.value = []
|
||||
overseasWarehousesError.value = errorMessage(cause)
|
||||
} finally { overseasWarehousesLoading.value = false }
|
||||
}
|
||||
function read(source: object, key: string) { return Reflect.get(source, key) as unknown }
|
||||
function text(value: unknown) { return value === null || value === undefined || value === '' ? '—' : String(value) }
|
||||
function money(amount: unknown, currency: unknown) { return amount === null || amount === undefined ? '—' : `${text(currency)} ${Number(amount).toFixed(2)}` }
|
||||
@@ -319,7 +347,7 @@ async function submit() {
|
||||
packageCount: form.packageCount,
|
||||
totalWeightKg: form.totalWeightKg, totalVolumeCbm: form.totalVolumeCbm,
|
||||
budgetAmount: form.budgetAmount, currency: form.currency || undefined,
|
||||
remark: form.remark || undefined,
|
||||
remark: props.kind === 'looseboard' ? [form.cargoName, form.remark].filter(Boolean).join(' · ') || undefined : form.remark || undefined,
|
||||
idempotencyKey: stableSubmitKey,
|
||||
}
|
||||
if (props.kind === 'trucking') {
|
||||
@@ -344,7 +372,7 @@ async function submit() {
|
||||
}
|
||||
|
||||
watch(() => [query.page, query.size], () => { if (!props.createPage && mode.value === 'list') void load() })
|
||||
onMounted(() => { if (props.detailPage) void loadDetail(); else if (!props.createPage) void load() })
|
||||
onMounted(() => { if (props.detailPage) void loadDetail(); else if (!props.createPage) void load(); void loadOverseasWarehouses() })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -402,6 +430,42 @@ onMounted(() => { if (props.detailPage) void loadDetail(); else if (!props.creat
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<el-card v-else-if="kind === 'looseboard'" shadow="never" class="form-card looseboard-form-card">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
|
||||
<div class="form-section transport-section">
|
||||
<h3><span class="section-icon"><Truck :size="14" /></span>运输方式 <em>*</em></h3>
|
||||
<div class="mode-buttons"><el-radio-group v-model="form.transportMode" @change="selectOriginMode"><el-radio-button value="SHARED_TRUCK"><Truck :size="14" /> 自送</el-radio-button><el-radio-button value="PRIORITY_CONSOLIDATION"><Package :size="14" /> 我司提货</el-radio-button></el-radio-group></div>
|
||||
</div>
|
||||
<div class="form-grid two-col">
|
||||
<el-form-item :label="isSelfDelivery ? '入仓库' : '取货地'" prop="origin" :rules="[{ required: true, message: isSelfDelivery ? '请选择美国仓库' : '请输入取货地', trigger: isSelfDelivery ? 'change' : 'blur' }]">
|
||||
<template v-if="isSelfDelivery">
|
||||
<el-select v-model="form.origin" filterable clearable :loading="overseasWarehousesLoading" placeholder="请选择美国仓库" no-data-text="暂无可用的美国海外仓">
|
||||
<el-option v-for="warehouse in overseasWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouseLabel(warehouse)" />
|
||||
</el-select>
|
||||
<div v-if="overseasWarehousesError" role="alert" class="warehouse-load-error">{{ overseasWarehousesError }} <el-button link type="primary" @click="loadOverseasWarehouses">重新加载</el-button></div>
|
||||
</template>
|
||||
<el-input v-else v-model.trim="form.origin" maxlength="255" placeholder="城市 / 港口 / 州" />
|
||||
</el-form-item>
|
||||
<el-form-item label="目的地" prop="destination"><el-input v-model.trim="form.destination" maxlength="255" placeholder="城市 / 州" /></el-form-item>
|
||||
<el-form-item label="联系人" prop="contactName"><el-input v-model.trim="form.contactName" maxlength="128" placeholder="联系人姓名" /></el-form-item>
|
||||
<el-form-item label="联系方式" prop="contactInfo"><el-input v-model.trim="form.contactInfo" maxlength="128" placeholder="联系电话" /></el-form-item>
|
||||
</div>
|
||||
<div class="form-section cargo-section"><h3><span class="section-icon"><Package :size="14" /></span>托载</h3><div class="form-grid cargo-grid">
|
||||
<el-form-item label="商品名称"><el-input v-model.trim="form.cargoName" placeholder="请输入商品名称" /></el-form-item>
|
||||
<el-form-item label="托数"><el-input-number v-model="form.palletCount" :min="0" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="总重量 (kg)"><el-input-number v-model="form.totalWeightKg" :min="0" :precision="2" controls-position="right" /></el-form-item>
|
||||
<el-form-item label="总体积 (m³)"><el-input-number v-model="form.totalVolumeCbm" :min="0" :precision="3" controls-position="right" /></el-form-item>
|
||||
</div></div>
|
||||
<div class="form-grid two-col lower-grid">
|
||||
<el-form-item label="预算总价($)"><el-input-number v-model="form.budgetAmount" :min="0" :precision="2" controls-position="right" placeholder="0.00" /></el-form-item>
|
||||
<div></div>
|
||||
<el-form-item label="要求取货时间" prop="requestedDate"><el-date-picker v-model="form.requestedDate" type="date" value-format="YYYY-MM-DD" placeholder="年 / 月 / 日" /></el-form-item>
|
||||
<el-form-item label="送货到达时间"><el-date-picker v-model="form.deliveryDate" type="date" value-format="YYYY-MM-DD" placeholder="年 / 月 / 日" /></el-form-item>
|
||||
</div>
|
||||
<div class="form-actions"><el-button @click="returnToList">取消</el-button><el-button v-if="canCreate" type="primary" :loading="submitting" @click="submit">提交订单</el-button></div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card v-else shadow="never" class="form-card">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" :label-width="kind === 'trucking' ? '140px' : '120px'">
|
||||
<el-form-item v-if="kind === 'looseboard'" label="运输方式"><el-radio-group v-model="form.transportMode"><el-radio-button value="SHARED_TRUCK">拼车</el-radio-button><el-radio-button value="PRIORITY_CONSOLIDATION">优先拼载</el-radio-button></el-radio-group></el-form-item>
|
||||
@@ -469,6 +533,32 @@ onMounted(() => { if (props.detailPage) void loadDetail(); else if (!props.creat
|
||||
.form-card :deep(.el-input), .form-card :deep(.el-textarea), .form-card :deep(.el-select), .form-card :deep(.el-date-editor) { width: 100%; }
|
||||
.form-card .currency { width: 110px; margin-left: 12px; }
|
||||
|
||||
.warehouse-load-error { color: #b42318; font-size: 12px; }
|
||||
.looseboard-form-card { padding: 0; border: 1px solid #e7ebf1; border-radius: 10px; background: #fff; box-shadow: 0 8px 22px rgb(31 45 61 / 4%); }
|
||||
.looseboard-form-card :deep(.el-card__body) { padding: 0; }
|
||||
.looseboard-form-card :deep(.el-form) { max-width: none; }
|
||||
.looseboard-form-card :deep(.el-form-item) { margin-bottom: 16px; }
|
||||
.looseboard-form-card :deep(.el-form-item__label) { height: auto; padding-bottom: 7px; color: #27364d; font-size: 13px; font-weight: 600; line-height: 1.2; }
|
||||
.looseboard-form-card :deep(.el-input__wrapper), .looseboard-form-card :deep(.el-textarea__inner), .looseboard-form-card :deep(.el-select__wrapper), .looseboard-form-card :deep(.el-input-number) { min-height: 36px; border-radius: 8px; box-shadow: 0 0 0 1px #e1e6ed inset; }
|
||||
.looseboard-form-card :deep(.el-input-number) { width: 100%; }
|
||||
.form-section { padding: 25px 30px 5px; }
|
||||
.form-section h3 { display: flex; align-items: center; gap: 7px; margin: 0 0 15px; color: #27364d; font-size: 14px; }
|
||||
.form-section h3 em { color: #e05252; font-style: normal; }
|
||||
.section-icon { display: inline-flex; width: 20px; height: 20px; align-items: center; justify-content: center; border-radius: 5px; background: #fff3e0; color: #dd8d2c; }
|
||||
.mode-buttons :deep(.el-radio-group) { display: flex; gap: 10px; }
|
||||
.mode-buttons :deep(.el-radio-button__inner) { display: inline-flex; align-items: center; gap: 6px; height: 36px; padding: 0 18px; border: 1px solid #dce3eb; border-radius: 8px !important; color: #4b5563; box-shadow: none; }
|
||||
.mode-buttons :deep(.el-radio-button:first-child .el-radio-button__inner) { border-left: 1px solid #dce3eb; }
|
||||
.mode-buttons :deep(.el-radio-button__original-radio:checked + .el-radio-button__inner) { border-color: #5965d8; background: #f0f2ff; color: #4f5bd5; box-shadow: none; }
|
||||
.form-grid { display: grid; gap: 0 22px; padding: 0 30px; }
|
||||
.two-col { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.cargo-section { margin-top: 4px; padding-top: 7px; background: #fafafa; border-top: 1px solid #f0f2f5; border-bottom: 1px solid #f0f2f5; }
|
||||
.cargo-grid { grid-template-columns: 1.25fr 1.25fr 1.25fr 1.25fr; }
|
||||
.lower-grid { padding-top: 20px; }
|
||||
.lower-grid :deep(.el-date-editor) { width: 100%; }
|
||||
.form-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 5px; padding: 20px 30px 25px; border-top: 1px solid #f0f2f5; }
|
||||
.form-actions .el-button { min-width: 80px; height: 36px; border-radius: 7px; }
|
||||
.form-actions .el-button--primary { background: linear-gradient(135deg, #6774df, #7553aa); border: 0; box-shadow: 0 5px 12px rgb(103 116 223 / 24%); }
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.business-orders { gap: 14px; }
|
||||
.page-head { align-items: flex-start; gap: 12px; }
|
||||
|
||||
Reference in New Issue
Block a user