Files
admin-web/src/modules/overseas-warehouse/components/WarehouseTransferReviewDialog.vue
T
2026-09-22 08:47:43 +08:00

88 lines
6.0 KiB
Vue

<script setup lang="ts">
import '@/styles/adminDensity.css'
import { computed, reactive, ref, watch } from 'vue'
import { CheckCircle2, XCircle } from '@lucide/vue'
import { ElMessage } from 'element-plus'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import { getWarehouseTransfer, reviewWarehouseTransfer, type WarehouseTransferDetailDto } from '../api/warehouseTransfers'
const props = defineProps<{ modelValue: boolean; customerId: string; customerName: string; transferNo: string }>()
const emit = defineEmits<{ 'update:modelValue': [value: boolean]; reviewed: [] }>()
const open = computed({ get: () => props.modelValue, set: (value) => emit('update:modelValue', value) })
const detail = ref<WarehouseTransferDetailDto | null>(null)
const loading = ref(false)
const saving = ref(false)
const error = ref<ApiErrorDetails | null>(null)
const form = reactive<{ decision: 'APPROVE' | 'REJECT'; remark: string }>({ decision: 'APPROVE', remark: '' })
const itemColumns: DataTableColumn[] = [
{ key: 'skuCode', label: 'SKU', minWidth: 140 }, { key: 'productNameZh', label: '品名', minWidth: 160 },
{ key: 'packageCount', label: '箱数', width: 80, align: 'right' }, { key: 'shippingMark', label: '客户箱唛', minWidth: 160 },
]
const bindingColumns: DataTableColumn[] = [
{ key: 'operationType', label: '换标类型', minWidth: 120 }, { key: 'customerCartonMark', label: '客户箱唛', minWidth: 150 },
{ key: 'originalSku', label: '原 SKU', minWidth: 120 }, { key: 'newSku', label: '新 SKU', minWidth: 120 },
{ key: 'quantity', label: '数量', width: 80, align: 'right' }, { key: 'fbaCartonMark', label: '新箱唛', minWidth: 150 },
]
const palletColumns: DataTableColumn[] = [
{ key: 'sequenceNo', label: '托序', width: 70, align: 'center' }, { key: 'palletNo', label: '托盘号', minWidth: 150 },
{ key: 'cartonCount', label: '箱数', width: 80, align: 'right' }, { key: 'volumeCbm', label: '托盘总体积(m³)', width: 150, align: 'right' },
{ key: 'grossWeightKg', label: '托盘总重量(kg)', width: 150, align: 'right' },
]
async function load() {
if (!props.customerId || !props.transferNo) return
loading.value = true; error.value = null; detail.value = null
try { detail.value = await getWarehouseTransfer(props.customerId, props.transferNo) }
catch (cause) { error.value = getApiErrorDetails(cause, '转运单审核资料加载失败,请重试') }
finally { loading.value = false }
}
async function submit() {
if (saving.value) return
if (form.decision === 'REJECT' && !form.remark.trim()) { ElMessage.warning('驳回时请填写审核原因'); return }
saving.value = true
try {
await reviewWarehouseTransfer(props.customerId, props.transferNo, form.decision, form.remark.trim() || undefined)
ElMessage.success(form.decision === 'APPROVE' ? '转运单审核通过,已推送 PDA 收货打托' : '转运单已驳回')
open.value = false; emit('reviewed')
} catch (cause) { ElMessage.error(getApiErrorDetails(cause, '审核提交失败,请重试').message) }
finally { saving.value = false }
}
watch(
() => [props.modelValue, props.customerId, props.transferNo] as const,
([value]) => {
if (!value) return
form.decision = 'APPROVE'
form.remark = ''
void load()
},
{ immediate: true },
)
</script>
<template>
<AppDialog class="admin-density-dialog" v-model="open" title="审核仓储转运单" width="min(920px, calc(100vw - 28px))" confirm-text="提交审核" :confirm-loading="saving" :loading="loading" @confirm="submit">
<LoadErrorState v-if="error" compact title="审核资料加载失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<el-form v-else-if="detail" label-position="top" class="review-form density-form">
<div class="review-heading"><strong>{{ detail.transferNo }}</strong><span>{{ customerName }} · {{ detail.packageCount }} 箱 · {{ detail.needRelabel ? '需要换标' : '无需换标' }}</span></div>
<h3>货物明细</h3><DataTable :data="detail.items" :columns="itemColumns" :show-pagination="false" row-key="id" empty-text="暂无货物明细" />
<template v-if="detail.needRelabel"><h3>换标绑定明细</h3><DataTable :data="detail.relabelBindings" :columns="bindingColumns" :show-pagination="false" row-key="customerCartonMark" empty-text="暂无换标绑定明细" /></template>
<template v-if="detail.pallets?.length"><h3>PDA 打托实测</h3><DataTable :data="detail.pallets" :columns="palletColumns" :show-pagination="false" row-key="id" empty-text="暂无打托数据" /></template>
<el-form-item label="审核结论" required><el-radio-group v-model="form.decision"><el-radio-button value="APPROVE"><CheckCircle2 :size="15" />审核通过</el-radio-button><el-radio-button value="REJECT"><XCircle :size="15" />驳回</el-radio-button></el-radio-group></el-form-item>
<el-form-item :label="form.decision === 'REJECT' ? '驳回原因' : '审核备注'" :required="form.decision === 'REJECT'"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="请输入审核备注(选填)" /></el-form-item>
</el-form>
</AppDialog>
</template>
<style scoped>
.review-form { display: grid; gap: var(--admin-space-5); }
.review-heading { display: flex; align-items: baseline; justify-content: space-between; gap: var(--admin-space-5); }
.review-heading span { color: var(--cc-muted); font-size: var(--admin-font-caption); }
h3 { margin: 2px 0 0; font-size: var(--admin-font-section); }
.review-form :deep(.el-form-item) { margin: var(--admin-space-1) 0 0; }
.review-form :deep(.el-radio-button__inner) { display: inline-flex; align-items: center; gap: var(--admin-space-2); }
</style>