84 lines
11 KiB
Vue
84 lines
11 KiB
Vue
<script setup lang="ts">
|
||
import { computed, reactive, ref, watch } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { ArrowLeft, Save } from '@lucide/vue'
|
||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
||
import FormSection from '@/shared/components/FormSection.vue'
|
||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
|
||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
|
||
import { getOverseasPurchase, listOverseasPurchaseWarehouseOptions, updateOverseasPurchase } from '../api/platformOverseasBusiness'
|
||
import { overseasBusinessPermissions } from '../permissions'
|
||
import type { OverseasPurchaseStatus, OverseasPurchaseWarehouseOption, UpdateOverseasPurchaseRequest } from '../types'
|
||
|
||
interface PurchaseEditModel {
|
||
orderNo: string; purchaseCategory: string; productName: string; productSpecification: string | null; productUrl: string | null
|
||
inboundQuantity: number; status: OverseasPurchaseStatus; purchaseWarehouseId: string | null
|
||
domesticCarrier: string | null; domesticTrackingNo: string | null; overseasCarrier: string | null; overseasTrackingNo: string | null
|
||
estimatedArrivalAt: string | null; remark: string | null; version: number
|
||
}
|
||
const route = useRoute(); const router = useRouter()
|
||
const purchaseId = computed(() => typeof route.params.purchaseId === 'string' ? route.params.purchaseId : '')
|
||
const customerId = computed(() => typeof route.query.customerId === 'string' ? route.query.customerId : '')
|
||
const formRef = ref<FormInstance>(); const loading = ref(false); const saving = ref(false)
|
||
const loadError = ref<ApiErrorDetails | null>(null); const submitError = ref<ApiErrorDetails | null>(null)
|
||
const warehouses = ref<OverseasPurchaseWarehouseOption[]>([]); const warehousesLoading = ref(false); const warehousesError = ref('')
|
||
const initial = (): PurchaseEditModel => ({ orderNo: '', purchaseCategory: '', productName: '', productSpecification: null, productUrl: null, inboundQuantity: 0, status: 'PENDING_REVIEW', purchaseWarehouseId: null, domesticCarrier: null, domesticTrackingNo: null, overseasCarrier: null, overseasTrackingNo: null, estimatedArrivalAt: null, remark: null, version: 0 })
|
||
const form = reactive<PurchaseEditModel>(initial())
|
||
const rules: FormRules<PurchaseEditModel> = {
|
||
purchaseCategory: [{ required: true, message: '请输入采购品类', trigger: 'blur' }],
|
||
productName: [{ required: true, message: '请输入商品名称', trigger: 'blur' }],
|
||
inboundQuantity: [{ required: true, type: 'number', min: 0, message: '入库量不能小于 0', trigger: 'change' }],
|
||
}
|
||
const statusLabels: Record<OverseasPurchaseStatus, string> = { PENDING_REVIEW: '待审核', APPROVED: '已通过', REJECTED: '已拒绝' }
|
||
const inboundEditable = computed(() => form.status === 'APPROVED')
|
||
const { captureBaseline } = useUnsavedWorkRegistration({ key: 'overseas-purchase-editor', label: computed(() => '编辑海外采购订单'), isOpen: computed(() => true), model: form, requiredPermission: computed(() => overseasBusinessPermissions.purchaseEdit) })
|
||
async function loadWarehouses() {
|
||
warehousesLoading.value = true; warehousesError.value = ''
|
||
try { warehouses.value = await listOverseasPurchaseWarehouseOptions() }
|
||
catch { warehouses.value = []; warehousesError.value = '采购仓候选加载失败,请稍后重试' }
|
||
finally { warehousesLoading.value = false }
|
||
}
|
||
async function load() {
|
||
Object.assign(form, initial()); loadError.value = null; submitError.value = null
|
||
if (!purchaseId.value || !customerId.value) { loadError.value = { message: '缺少采购订单号或客户标识,请从采购订单列表重新进入' }; captureBaseline(); return }
|
||
loading.value = true
|
||
try {
|
||
const detail = await getOverseasPurchase(purchaseId.value, customerId.value)
|
||
Object.assign(form, { orderNo: detail.orderNo, purchaseCategory: detail.purchaseCategory, productName: detail.productName, productSpecification: detail.productSpecification, productUrl: detail.productUrl, inboundQuantity: detail.inboundQuantity, status: detail.status, purchaseWarehouseId: detail.purchaseWarehouseId, domesticCarrier: detail.domesticCarrier, domesticTrackingNo: detail.domesticTrackingNo, overseasCarrier: detail.overseasCarrier, overseasTrackingNo: detail.overseasTrackingNo, estimatedArrivalAt: detail.estimatedArrivalAt, remark: detail.remark, version: detail.version })
|
||
if (detail.purchaseWarehouseId && !warehouses.value.some((item) => item.warehouseId === detail.purchaseWarehouseId)) warehouses.value.push({ warehouseId: detail.purchaseWarehouseId, warehouseCode: '', warehouseName: detail.purchaseWarehouseName || '当前采购仓', countryCode: '' })
|
||
captureBaseline()
|
||
} catch (error) { loadError.value = getApiErrorDetails(error, '海外采购订单加载失败') } finally { loading.value = false }
|
||
}
|
||
async function save() {
|
||
if (!await formRef.value?.validate().catch(() => false)) return
|
||
saving.value = true; submitError.value = null
|
||
const payload: UpdateOverseasPurchaseRequest = { version: form.version, purchaseCategory: form.purchaseCategory.trim(), productName: form.productName.trim(), productSpecification: form.productSpecification?.trim() || null, productUrl: form.productUrl?.trim() || null, inboundQuantity: inboundEditable.value ? form.inboundQuantity : undefined, purchaseWarehouseId: form.purchaseWarehouseId || null, domesticCarrier: form.domesticCarrier?.trim() || null, domesticTrackingNo: form.domesticTrackingNo?.trim() || null, overseasCarrier: form.overseasCarrier?.trim() || null, overseasTrackingNo: form.overseasTrackingNo?.trim() || null, estimatedArrivalAt: form.estimatedArrivalAt || null, remark: form.remark?.trim() || null }
|
||
try {
|
||
const updated = await updateOverseasPurchase(purchaseId.value, customerId.value, payload)
|
||
form.version = updated.version; captureBaseline(); ElMessage.success('海外采购订单已保存')
|
||
await router.push({ name: 'overseas-business-purchase-detail', params: { purchaseId: updated.orderNo }, query: { customerId: updated.customerId } })
|
||
} catch (error) { submitError.value = getApiErrorDetails(error, '海外采购订单保存失败') } finally { saving.value = false }
|
||
}
|
||
watch([purchaseId, customerId], () => { void loadWarehouses().then(load) }, { immediate: true })
|
||
</script>
|
||
|
||
<template>
|
||
<AdminPage width="comfortable"><OperationErrorToast :details="submitError" /><DetailPageLayout aria-label="编辑海外采购订单">
|
||
<template #header><div class="form-header"><div><h2>编辑海外采购订单</h2><span>订单编号:{{ form.orderNo || purchaseId || '—' }}</span></div><el-button @click="router.push({ name: 'overseas-business-purchase-detail', params: { purchaseId }, query: { customerId } })"><ArrowLeft :size="16" />返回</el-button></div></template>
|
||
<LoadErrorState v-if="loadError" title="采购订单暂时无法编辑" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState>
|
||
<section v-else v-loading="loading" class="surface-panel form-panel"><el-form ref="formRef" :model="form" :rules="rules" label-position="top" :disabled="saving">
|
||
<FormSection title="商品信息"><div class="form-grid three"><el-form-item label="订单编号"><el-input v-model="form.orderNo" disabled /></el-form-item><el-form-item label="采购品类" prop="purchaseCategory"><el-input v-model="form.purchaseCategory" maxlength="128" /></el-form-item><el-form-item label="商品名称" prop="productName"><el-input v-model="form.productName" maxlength="255" /></el-form-item><el-form-item class="span-2" label="商品规格"><el-input v-model="form.productSpecification" maxlength="500" /></el-form-item><el-form-item class="span-3" label="商品链接"><el-input v-model="form.productUrl" maxlength="2000" /></el-form-item></div></FormSection>
|
||
<FormSection title="库存与快递信息"><div class="form-grid three"><el-form-item label="入库量" prop="inboundQuantity"><el-input-number v-model="form.inboundQuantity" :min="0" :disabled="!inboundEditable" controls-position="right" /><small v-if="!inboundEditable">服务端仅允许已通过订单修改入库量</small></el-form-item><el-form-item label="状态"><el-input :model-value="statusLabels[form.status]" disabled /></el-form-item><el-form-item label="采购仓库"><el-select v-model="form.purchaseWarehouseId" clearable filterable :loading="warehousesLoading" :disabled="Boolean(warehousesError)" placeholder="请选择采购仓"><el-option v-for="item in warehouses" :key="item.warehouseId" :label="`${item.warehouseName}${item.warehouseCode ? ` · ${item.warehouseCode}` : ''}`" :value="item.warehouseId" /></el-select><small v-if="warehousesError" class="field-error">{{ warehousesError }}</small></el-form-item><el-form-item label="预计到达时间"><el-date-picker v-model="form.estimatedArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" clearable /></el-form-item><el-form-item label="国内承运商"><el-input v-model="form.domesticCarrier" maxlength="128" /></el-form-item><el-form-item label="国内运单号"><el-input v-model="form.domesticTrackingNo" maxlength="128" /></el-form-item><el-form-item label="海外承运商"><el-input v-model="form.overseasCarrier" maxlength="128" /></el-form-item><el-form-item label="海外运单号"><el-input v-model="form.overseasTrackingNo" maxlength="128" /></el-form-item><el-form-item class="span-3" label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="1000" show-word-limit /></el-form-item></div></FormSection>
|
||
</el-form><footer class="form-actions"><el-button type="primary" :loading="saving" @click="save"><Save :size="16" />保存</el-button><el-button :disabled="saving" @click="router.push({ name: 'overseas-business-purchase-detail', params: { purchaseId }, query: { customerId } })">取消</el-button></footer></section>
|
||
</DetailPageLayout></AdminPage>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.form-header { display:flex; align-items:center; justify-content:space-between; gap:16px; padding:14px 18px; border:1px solid var(--cc-border); border-radius:8px; background:#fff; }.form-header h2 { margin:0; font-size:16px; }.form-header span { display:block; margin-top:4px; color:var(--cc-muted); font-size:12px; }.form-panel { padding:20px 24px; }.form-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:0 20px; }.span-2 { grid-column:span 2; }.span-3 { grid-column:1 / -1; }.form-grid :deep(.el-select), .form-grid :deep(.el-date-editor), .form-grid :deep(.el-input-number) { width:100%; }.form-grid small { display:block; margin-top:5px; color:var(--cc-muted); font-size:12px; }.form-grid .field-error { color:var(--el-color-danger); }.form-actions { display:flex; justify-content:flex-end; gap:12px; padding-top:16px; border-top:1px solid var(--cc-border); }
|
||
</style>
|