2026-08-13 11:57:44 +08:00
< script setup lang = "ts" >
2026-09-14 15:33:35 +08:00
import '@/styles/adminDensity.css'
import '../measurement/measurementDensity.css'
2026-08-13 19:38:20 +08:00
import { computed , onActivated , onBeforeUnmount , onMounted , ref , watch } from 'vue'
2026-08-13 11:57:44 +08:00
import { useRouter } from 'vue-router'
2026-08-17 18:00:42 +08:00
import { ElMessage , ElMessageBox } from 'element-plus'
2026-09-14 17:54:21 +08:00
import { CircleCheckBig } from '@lucide/vue'
2026-08-13 11:57:44 +08:00
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { listPlatformTenants , type PlatformTenantResource } from '@/modules/iam'
import { listPlatformWarehouses } from '@/modules/warehouses'
import { getApiErrorDetails , type ApiErrorDetails } from '@/shared/api/http'
import AdminPage from '@/shared/components/AdminPage.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
2026-10-20 10:11:31 +08:00
import AppDialog from '@/shared/components/AppDialog.vue'
2026-08-13 11:57:44 +08:00
import DataTable , { type DataTableColumn } from '@/shared/components/DataTable.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
2026-09-03 21:25:56 +08:00
import LineClampText from '@/shared/components/LineClampText.vue'
2026-08-13 11:57:44 +08:00
import QueryToolbar , { type QueryField , type QueryModel } from '@/shared/components/QueryToolbar.vue'
2026-09-02 14:39:34 +08:00
import { firstLegBusinessTypeLabel } from '@/shared/utils/firstLegBusinessLabels'
2026-08-13 11:57:44 +08:00
import { listPlatformInbounds } from '../core/api'
import type { InboundOrderSummary } from '../core/types'
2026-08-17 17:49:10 +08:00
import InboundMeasurementDialog from '../measurement/InboundMeasurementDialog.vue'
2026-08-13 11:57:44 +08:00
import MeasurementReviewPanel from '../review/MeasurementReviewPanel.vue'
2026-08-17 18:00:42 +08:00
import { approveMeasurement , getMeasurementReviewDetail } from '../review/api'
2026-08-13 11:57:44 +08:00
import { inboundProcessStatusLabels , inboundReviewStatusLabels , inboundStatusLabel } from '../status'
2026-08-14 14:27:03 +08:00
import { buildInboundTaskQuery , loadAllActiveCustomers , type InboundTaskMode } from '../task-list'
2026-08-13 11:57:44 +08:00
type FilterModel = QueryModel & {
customerId : string
inboundCode : string
warehouseId : string
expectedArrivalRange : string []
}
2026-10-19 15:59:14 +08:00
const props = defineProps < { mode : InboundTaskMode ; title ?: string } > ()
2026-08-13 11:57:44 +08:00
const router = useRouter ()
const access = useAccessPreviewStore ()
2026-10-20 10:11:31 +08:00
const reviewPanelRef = ref < InstanceType < typeof MeasurementReviewPanel > | null > ( null )
2026-08-13 11:57:44 +08:00
const page = ref ( 1 )
const pageSize = ref ( 20 )
const loading = ref ( false )
const loadError = ref < ApiErrorDetails | null >( null )
const result = ref ({ records : [] as InboundOrderSummary [], total : 0 })
2026-08-14 19:59:32 +08:00
const filters = ref < FilterModel >({ customerId : '' , inboundCode : '' , warehouseId : '' , expectedArrivalRange : [] })
2026-08-13 11:57:44 +08:00
const appliedFilters = ref < FilterModel >({ ... filters . value , expectedArrivalRange : [] })
const customers = ref < PlatformTenantResource [] >([])
const customerLoadError = ref < ApiErrorDetails | null >( null )
const warehouseOptions = ref < Array < { label : string ; value : string }> > ([])
const selectedInbound = ref < InboundOrderSummary | null >( null )
2026-08-17 17:49:10 +08:00
const measurementDialogOpen = ref ( false )
const reviewDialogOpen = ref ( false )
2026-09-01 20:49:04 +08:00
const reviewNavigationLoading = ref ( false )
const reviewApproved = ref ( false )
const reviewNavigationError = ref < ApiErrorDetails | null >( null )
2026-08-17 18:00:42 +08:00
const batchReviewLoading = ref ( false )
const selectedReviewRows = ref < InboundOrderSummary [] >([])
2026-08-13 11:57:44 +08:00
let requestSerial = 0
2026-08-14 14:27:03 +08:00
let optionsRequestSerial = 0
2026-08-13 11:57:44 +08:00
let disposed = false
2026-08-13 19:38:20 +08:00
let activatedOnce = false
2026-08-13 11:57:44 +08:00
const isReview = computed (() => props . mode === 'review' )
const actionPermission = computed (() => isReview . value
? 'firstleg:inbound:measurement-review'
: 'platform:firstleg:inbound:measure:view' )
const taskLabel = computed (() => isReview . value ? '审核' : '测量' )
const emptyText = computed (() => isReview . value ? '暂无待审核的测量数据' : '暂无当前可测量的订单' )
const preferenceKey = computed (() => isReview . value ? 'inbound-measurement-reviews' : 'inbound-measurements' )
2026-09-18 11:41:27 +08:00
const customerNameById = computed (() => new Map ( customers . value . map (( customer ) => [
String ( customer . id ), customer . legalName ? . trim () || customer . tenantName ,
])))
2026-08-13 11:57:44 +08:00
const queryFields = computed < QueryField [] >(() => [
{
key : 'customerId' ,
type : 'select' ,
label : '客户' ,
width : 260 ,
2026-08-26 18:13:12 +08:00
placeholder : customerLoadError . value ? '客户列表加载失败' : '请选择客户' ,
2026-08-13 11:57:44 +08:00
options : customers . value . map (( customer ) => ({
value : String ( customer . id ),
2026-08-14 12:50:42 +08:00
label : customer . legalName ? . trim () || customer . tenantName ,
2026-08-13 11:57:44 +08:00
})),
props : { filterable : true , disabled : Boolean ( customerLoadError . value ) },
},
2026-08-26 18:13:12 +08:00
{ key : 'warehouseId' , type : 'select' , label : '定位仓库' , width : 220 , placeholder : '请选择仓库' , options : warehouseOptions . value , props : { filterable : true } },
2026-08-13 11:57:44 +08:00
{ key : 'inboundCode' , type : 'input' , label : '入仓码' , width : 220 , placeholder : '请输入入仓码' },
{ key : 'expectedArrivalRange' , type : 'daterange' , label : '预计到达时间' , width : 320 , placeholder : '请选择日期范围' },
])
2026-08-17 18:00:42 +08:00
const columns = computed < DataTableColumn [] >(() => [
2026-09-03 21:25:56 +08:00
{ key : 'actions' , label : '操作' , width : 160 , fixed : 'left' , slotName : 'actions' },
2026-08-17 18:00:42 +08:00
...( isReview . value && access . can ( actionPermission . value ) ? [{
key : 'selection' , type : 'selection' , width : 48 , fixed : 'left' ,
selectable : ( row : InboundOrderSummary ) => row . measureReviewStatus === 'PENDING' && ! batchReviewLoading . value ,
} satisfies DataTableColumn ] : []),
2026-09-03 21:25:56 +08:00
{ key : 'inboundCode' , label : '入仓码' , minWidth : 140 , slotName : 'inboundCode' },
2026-08-13 11:57:44 +08:00
{ key : 'processStatus' , label : '流程状态' , minWidth : 132 , slotName : 'processStatus' },
2026-09-03 21:25:56 +08:00
{ key : 'measureReviewStatus' , label : '测量审核状态' , minWidth : 120 , slotName : 'measureReviewStatus' },
{ key : 'expectedArrivalAt' , label : '预计到达时间' , minWidth : 140 , slotName : 'expectedArrivalAt' },
{ key : 'customerName' , label : '客户' , minWidth : 140 , slotName : 'customerName' },
{ key : 'warehouseName' , label : '仓库' , minWidth : 120 , slotName : 'warehouseName' },
{ key : 'deliveryWarehouseName' , label : '交货仓' , minWidth : 120 , slotName : 'deliveryWarehouseName' },
{ key : 'businessType' , label : '业务类型' , minWidth : 100 , slotName : 'businessType' },
{ key : 'channelName' , label : '渠道' , minWidth : 100 , slotName : 'channelName' },
{ key : 'totalPackageCount' , label : '总箱数' , width : 90 , slotName : 'totalPackageCount' },
{ key : 'palletCount' , label : '托盘数' , width : 90 , slotName : 'palletCount' },
{ key : 'actualGrossWeightKg' , label : '实测毛重(kg)' , minWidth : 100 , slotName : 'actualGrossWeightKg' },
{ key : 'actualNetWeightKg' , label : '货物重量(kg)' , minWidth : 100 , slotName : 'actualNetWeightKg' },
{ key : 'actualVolumeCbm' , label : '实测体积(m³)' , minWidth : 100 , slotName : 'actualVolumeCbm' },
{ key : 'measureCompletedAt' , label : '提交时间' , minWidth : 140 , slotName : 'measureCompletedAt' },
2026-08-17 18:00:42 +08:00
])
2026-08-13 11:57:44 +08:00
function text ( value : unknown ) {
return value === null || value === undefined || value === '' ? '—' : String ( value )
}
2026-09-18 11:41:27 +08:00
function customerDisplayName ( inbound : InboundOrderSummary ) {
const snapshot = inbound . customerName ? . trim () || inbound . companyName ? . trim ()
return snapshot || customerNameById . value . get ( String ( inbound . customerId || inbound . tenantId )) || ''
}
2026-08-13 11:57:44 +08:00
function formatNumber ( value : number | null | undefined ) {
return value === null || value === undefined ? '—' : value . toLocaleString ( 'zh-CN' , { maximumFractionDigits : 3 })
}
function formatDate ( value : string | null | undefined ) {
if ( ! value ) return '—'
const date = new Date ( value )
return Number . isNaN ( date . getTime ()) ? value : date . toLocaleString ( 'zh-CN' , { hour12 : false })
}
2026-09-04 15:06:39 +08:00
function formatDateOnly ( value : string | null | undefined ) {
if ( ! value ) return '—'
const date = new Date ( value )
return Number . isNaN ( date . getTime ()) ? value : date . toLocaleDateString ( 'zh-CN' )
}
2026-08-13 11:57:44 +08:00
function dateParam ( value : string | undefined , end = false ) {
return value ? ` ${ value } T ${ end ? '23:59:59' : '00:00:00' } ` : undefined
}
async function loadOptions () {
2026-08-14 14:27:03 +08:00
const serial = ++ optionsRequestSerial
2026-08-13 11:57:44 +08:00
customerLoadError . value = null
try {
2026-08-14 14:27:03 +08:00
const [ customerRecords , warehousePage ] = await Promise . all ([
loadAllActiveCustomers (( query ) => listPlatformTenants ( query )),
2026-08-13 11:57:44 +08:00
listPlatformWarehouses ( 1 , 200 ),
])
2026-08-14 14:27:03 +08:00
if ( disposed || serial !== optionsRequestSerial ) return
customers . value = customerRecords
2026-08-13 11:57:44 +08:00
warehouseOptions . value = warehousePage . records . map (( warehouse ) => ({
label : ` ${ warehouse . warehouseName } · ${ warehouse . warehouseCode } ` ,
value : String ( warehouse . id ),
}))
} catch ( error ) {
if ( disposed ) return
customerLoadError . value = getApiErrorDetails ( error , '客户或仓库筛选项加载失败,不影响查看全部待办' )
}
}
async function loadTasks () {
2026-08-17 18:00:42 +08:00
selectedReviewRows . value = []
2026-08-13 11:57:44 +08:00
const serial = ++ requestSerial
loading . value = true
loadError . value = null
const applied = appliedFilters . value
try {
const response = await listPlatformInbounds ( applied . customerId || undefined , buildInboundTaskQuery ( props . mode , {
page : page . value ,
size : pageSize . value ,
warehouseId : applied . warehouseId || undefined ,
inboundCode : applied . inboundCode . trim () || undefined ,
expectedArrivalFrom : dateParam ( applied . expectedArrivalRange [ 0 ]),
expectedArrivalTo : dateParam ( applied . expectedArrivalRange [ 1 ], true ),
}))
if ( disposed || serial !== requestSerial ) return
result . value = { records : response . records , total : response . total }
} catch ( error ) {
if ( disposed || serial !== requestSerial ) return
loadError . value = getApiErrorDetails ( error , ` ${ taskLabel . value } 待办加载失败` )
} finally {
if ( ! disposed && serial === requestSerial ) loading . value = false
}
}
function applyFilters () {
2026-08-26 18:13:12 +08:00
appliedFilters . value = {
... filters . value ,
expectedArrivalRange : Array . isArray ( filters . value . expectedArrivalRange ) ? [... filters . value . expectedArrivalRange ] : [],
}
2026-08-13 11:57:44 +08:00
if ( page . value === 1 ) void loadTasks ()
else page . value = 1
}
function resetFilters () {
2026-08-14 19:59:32 +08:00
filters . value = { customerId : '' , inboundCode : '' , warehouseId : '' , expectedArrivalRange : [] }
2026-08-13 11:57:44 +08:00
appliedFilters . value = { ... filters . value , expectedArrivalRange : [] }
if ( page . value === 1 ) void loadTasks ()
else page . value = 1
}
function openTask ( row : InboundOrderSummary ) {
selectedInbound . value = row
2026-09-01 20:49:04 +08:00
if ( isReview . value ) {
reviewApproved . value = false
reviewNavigationError . value = null
reviewDialogOpen . value = true
}
2026-08-17 17:49:10 +08:00
else measurementDialogOpen . value = true
2026-08-13 11:57:44 +08:00
}
function openDetail ( row : InboundOrderSummary ) {
2026-08-13 19:00:37 +08:00
if ( ! row . orderId ) return
void router . push ({ name : 'unified-order-detail' , params : { orderId : row . orderId }, query : { tenantId : row . tenantId } })
2026-08-13 11:57:44 +08:00
}
2026-09-01 20:49:04 +08:00
async function handleTaskChanged () {
const target = selectedInbound . value
if ( ! isReview . value ) {
reviewDialogOpen . value = false
selectedInbound . value = null
void loadTasks ()
return
}
if ( ! target || reviewNavigationLoading . value ) return
reviewApproved . value = true
reviewNavigationError . value = null
const inboundCode = target . inboundCode ? . trim ()
const orderId = target . orderId ? . trim ()
if ( ! inboundCode && ! orderId ) {
reviewNavigationError . value = { message : '审核已通过,但未找到对应订单,请关闭弹框后刷新列表重试' }
return
}
reviewNavigationLoading . value = true
try {
if ( inboundCode ) {
await router . push ({
name : 'unified-order-detail-by-inbound-code' ,
params : { inboundCode },
query : { tab : 'measurement' , from : 'measurement-review' },
})
} else {
await router . push ({
name : 'unified-order-detail' ,
params : { orderId },
query : { tenantId : target . tenantId , tab : 'measurement' , from : 'measurement-review' },
})
}
reviewDialogOpen . value = false
selectedInbound . value = null
} catch ( error ) {
reviewNavigationError . value = getApiErrorDetails ( error , '订单详情打开失败,请重试' )
} finally {
reviewNavigationLoading . value = false
}
2026-08-13 11:57:44 +08:00
}
2026-08-17 18:00:42 +08:00
function handleReviewSelectionChange ( rows : InboundOrderSummary []) {
selectedReviewRows . value = rows . filter (( row ) => row . measureReviewStatus === 'PENDING' )
}
async function batchApproveMeasurements () {
const selected = [... selectedReviewRows . value ]
if ( ! selected . length || batchReviewLoading . value ) return
batchReviewLoading . value = true
try {
await ElMessageBox . confirm (
2026-09-09 11:50:42 +08:00
`确认批量审核通过已勾选的 ${ selected . length } 条测量数据吗?一件代发通过后进入待组托,其他订单进入待分柜。` ,
2026-08-17 18:00:42 +08:00
'批量审核测量数据' ,
2026-09-14 15:52:00 +08:00
{ customClass : 'admin-density-confirm' , confirmButtonText : '确认批量审核' , cancelButtonText : '取消' , type : 'warning' },
2026-08-17 18:00:42 +08:00
)
let successCount = 0
let failureCount = 0
const failures : string [] = []
2026-08-17 18:04:08 +08:00
const pricingWarnings : string [] = []
2026-08-17 18:00:42 +08:00
for ( const row of selected ) {
try {
const detail = await getMeasurementReviewDetail ( String ( row . tenantId ), String ( row . id ))
2026-08-17 18:04:08 +08:00
const approval = await approveMeasurement ( String ( row . tenantId ), String ( row . id ), {
2026-08-17 18:00:42 +08:00
measurementId : detail . measurementId ,
2026-08-27 21:48:15 +08:00
expectedVersion : detail . inboundVersion ,
2026-08-17 18:00:42 +08:00
})
successCount += 1
2026-08-17 18:04:08 +08:00
if ( ! approval . initialPricingSucceeded ) {
pricingWarnings . push (
2026-08-30 17:43:03 +08:00
` ${ row . inboundCode || '入仓码待补充' } : ${ approval . initialPricingMessage || '费用初算未生成' } ` ,
2026-08-17 18:04:08 +08:00
)
}
2026-08-17 18:00:42 +08:00
} catch ( error ) {
failureCount += 1
2026-08-30 17:43:03 +08:00
failures . push ( getApiErrorDetails ( error , ` ${ row . inboundCode || '入仓码待补充' } 审核失败` ). message )
2026-08-17 18:00:42 +08:00
}
}
await loadTasks ()
const reason = [... new Set ( failures . filter ( Boolean ))]. slice ( 0 , 3 ). join ( '; ' )
2026-08-17 18:04:08 +08:00
const pricingReason = [... new Set ( pricingWarnings . filter ( Boolean ))]. slice ( 0 , 3 ). join ( '; ' )
if ( ! failureCount && ! pricingWarnings . length ) ElMessage . success ( `已批量审核通过 ${ successCount } 条测量数据,费用初算已生成` )
else if ( ! failureCount ) ElMessage . warning ( `已批量审核通过 ${ successCount } 条测量数据;其中 ${ pricingWarnings . length } 条费用初算未生成,可补价后重新计算 ${ pricingReason ? `。 ${ pricingReason } ` : '' } ` )
2026-08-17 18:00:42 +08:00
else if ( successCount ) ElMessage . warning ( `批量审核完成:成功 ${ successCount } 条,失败 ${ failureCount } 条 ${ reason ? `。 ${ reason } ` : '' } ` )
else ElMessage . error ( `批量审核失败: ${ failureCount } 条测量数据未通过 ${ reason ? `。 ${ reason } ` : '' } ` )
} catch ( error ) {
if ( error !== 'cancel' && error !== 'close' ) ElMessage . error ( getApiErrorDetails ( error , '批量审核失败' ). message )
} finally {
batchReviewLoading . value = false
}
}
2026-08-17 11:23:31 +08:00
function handleMeasurementUpdated () {
void loadTasks ()
}
2026-08-17 17:49:10 +08:00
function handleMeasurementSubmitted () {
void loadTasks ()
}
function clearSelectedInbound () {
selectedInbound . value = null
2026-09-01 20:49:04 +08:00
reviewApproved . value = false
reviewNavigationError . value = null
reviewNavigationLoading . value = false
2026-08-14 14:50:44 +08:00
}
2026-08-13 11:57:44 +08:00
watch ([ page , pageSize ], () => { void loadTasks () })
onMounted (() => { void loadOptions (); void loadTasks () })
2026-08-13 19:38:20 +08:00
onActivated (() => {
if ( ! activatedOnce ) {
activatedOnce = true
return
}
void loadTasks ()
})
2026-08-14 14:27:03 +08:00
onBeforeUnmount (() => { disposed = true ; requestSerial += 1 ; optionsRequestSerial += 1 })
2026-08-13 11:57:44 +08:00
</ script >
< template >
2026-09-14 15:33:35 +08:00
< AdminPage as = "div" class = "inbound-task-page admin-density" >
2026-08-13 11:57:44 +08:00
< ListPageLayout :aria-label = "`${taskLabel}待办`" >
2026-09-30 14:01:58 +08:00
< section :aria-busy = "loading" >< DataTable appearance = "integrated" :stripe = "false" column -settings-mode =" show " :results-visible = "!loadError"
2026-08-13 11:57:44 +08:00
v -model:page = "page"
v -model:page-size = "pageSize"
:data = "result.records"
:columns = "columns"
:preference-key = "`${preferenceKey}-table`"
:loading = "loading"
:total = "result.total"
: page -sizes ="[ 20 , 50 , 100 ]"
row -key =" id "
:empty-text = "emptyText"
:aria-label = "`${taskLabel}待办列表`"
2026-08-17 18:00:42 +08:00
@ selection -change =" handleReviewSelectionChange "
2026-08-13 11:57:44 +08:00
>
2026-10-19 15:59:14 +08:00
< template # toolbar -start >< h1 >{{ title || ( isReview ? '测量数据审核' : '订单测量' ) }}</ h1 ></ template >
2026-09-30 14:01:58 +08:00
< template # filters >
2026-10-19 15:59:14 +08:00
< slot name = "status-tabs" />
2026-09-30 14:01:58 +08:00
< QueryToolbar
v-model = "filters"
:ariaLabel = "`${taskLabel}待办筛选`"
:fields = "queryFields"
:preference-key = "`${preferenceKey}-query`"
:search-on-reset = "false"
@search ="applyFilters"
@reset ="resetFilters"
/>
</ template >
< template # notices >< template v-if = "customerLoadError || loadError" >
< div class = "state-stack" >
< el-alert v-if = "customerLoadError" type="warning" :closable="false" show -icon >
< template # title >< span >{{ customerLoadError . message }}</ span ></ template >
</ el-alert >
< LoadErrorState v-if = "loadError" :title="`${taskLabel}待办暂时无法加载`" :message="loadError.message" @retry="loadTasks" >
< template # details >< ApiErrorMeta :details = "loadError" /></ template >
</ LoadErrorState >
</ div >
</ template ></ template >
2026-08-17 18:00:42 +08:00
< template v-if = "isReview && access.can(actionPermission)" # toolbar -actions >
< el-button type = "success" :loading = "batchReviewLoading" : disabled = "loading || !selectedReviewRows.length" @click ="batchApproveMeasurements" >< CircleCheckBig :size = "16" /> 批量审核通过 < span v-if = "selectedReviewRows.length" > ( {{ selectedReviewRows.length }} ) </ span > </ el -button >
</ template >
2026-08-13 11:57:44 +08:00
< template # pagination -left > 当前显示 {{ result . records . length }} 条 · 共 {{ result . total }} 条 </ template >
2026-09-30 14:01:58 +08:00
< template # actions = "{ row }" >< div class = "row-actions density-actions admin-text-actions" >
2026-09-14 17:54:21 +08:00
< el-button v-if = "access.can(actionPermission)" class="density-action" :data-tone="isReview ? 'warning' : 'purple'" size="small" @click="openTask(row as InboundOrderSummary)" >
2026-08-13 11:57:44 +08:00
{{ taskLabel }}
</ el -button >
2026-09-14 17:54:21 +08:00
< el-button v-if = "access.can('platform:firstleg:inbound:view') && (row as InboundOrderSummary).orderId" class="density-action" size="small" @click="openDetail(row as InboundOrderSummary)" >
查看
2026-08-13 11:57:44 +08:00
</ el -button >
2026-08-13 19:00:37 +08:00
< span v-if = "!access.can(actionPermission) && (!access.can('platform:firstleg:inbound:view') || !(row as InboundOrderSummary).orderId)" class="muted-action" > 无操作权限 </ span >
2026-09-30 14:01:58 +08:00
</ div ></ template >
2026-09-02 14:39:34 +08:00
< template # inboundCode = "{ row }" >< div class = "inbound-code-cell" >< strong >{{ text (( row as InboundOrderSummary ). inboundCode ) }}</ strong ></ div ></ template >
2026-08-13 11:57:44 +08:00
< template # processStatus = "{ row }" >< el-tag effect = "light" >{{ inboundStatusLabel (( row as InboundOrderSummary ). processStatus , inboundProcessStatusLabels ) }}</ el-tag ></ template >
< template # measureReviewStatus = "{ row }" >{{ inboundStatusLabel (( row as InboundOrderSummary ). measureReviewStatus , inboundReviewStatusLabels ) }}</ template >
2026-09-04 15:06:39 +08:00
< template # expectedArrivalAt = "{ row }" >{{ formatDateOnly (( row as InboundOrderSummary ). expectedArrivalAt ) }}</ template >
2026-09-03 21:25:56 +08:00
< template # customerName = "{ row }" >< LineClampText : content = "(row as InboundOrderSummary).customerName" /></ template >
2026-08-13 11:57:44 +08:00
< template # warehouseName = "{ row }" >{{ text (( row as InboundOrderSummary ). warehouseName ) }}</ template >
< template # deliveryWarehouseName = "{ row }" >{{ text (( row as InboundOrderSummary ). deliveryWarehouseName ) }}</ template >
2026-09-02 14:39:34 +08:00
< template # businessType = "{ row }" >{{ firstLegBusinessTypeLabel (( row as InboundOrderSummary ). businessType ) }}</ template >
2026-09-03 21:25:56 +08:00
< template # channelName = "{ row }" >< LineClampText : content = "(row as InboundOrderSummary).channelName || (row as InboundOrderSummary).channelCode" /></ template >
2026-08-13 11:57:44 +08:00
< template # totalPackageCount = "{ row }" >{{ text (( row as InboundOrderSummary ). totalPackageCount ) }}</ template >
2026-08-17 19:13:25 +08:00
< template # palletCount = "{ row }" >{{ text (( row as InboundOrderSummary ). palletCount ) }}</ template >
2026-08-13 11:57:44 +08:00
< template # actualGrossWeightKg = "{ row }" >{{ formatNumber (( row as InboundOrderSummary ). actualGrossWeightKg ) }}</ template >
2026-08-17 19:13:25 +08:00
< template # actualNetWeightKg = "{ row }" >{{ formatNumber (( row as InboundOrderSummary ). actualNetWeightKg ) }}</ template >
2026-08-13 11:57:44 +08:00
< template # actualVolumeCbm = "{ row }" >{{ formatNumber (( row as InboundOrderSummary ). actualVolumeCbm ) }}</ template >
< template # measureCompletedAt = "{ row }" >{{ formatDate (( row as InboundOrderSummary ). measureCompletedAt ) }}</ template >
< template # empty >< el-empty :description = "emptyText" /></ template >
2026-09-30 14:01:58 +08:00
</ DataTable ></ section >
2026-08-13 11:57:44 +08:00
</ ListPageLayout >
2026-10-20 10:11:31 +08:00
< AppDialog
2026-08-17 17:49:10 +08:00
v-if = "isReview"
v-model = "reviewDialogOpen"
2026-08-30 17:43:03 +08:00
: title = "selectedInbound ? `${taskLabel} · ${selectedInbound.inboundCode || '入仓码待补充'}` : taskLabel"
2026-09-14 15:33:35 +08:00
width = "min(1560px, calc(100vw - 48px))"
2026-08-13 11:57:44 +08:00
top = "4vh"
2026-10-20 10:11:31 +08:00
fullscreenable
:show-confirm = "false"
2026-08-13 11:57:44 +08:00
destroy -on-close
append -to-body
2026-09-01 20:49:04 +08:00
:close-on-click-modal = "!reviewNavigationLoading"
:close-on-press-escape = "!reviewNavigationLoading"
2026-09-14 15:33:35 +08:00
class = "inbound-task-dialog admin-density-dialog admin-density-workspace inbound-density"
2026-08-17 17:49:10 +08:00
@closed ="clearSelectedInbound"
2026-08-13 11:57:44 +08:00
>
2026-09-01 20:49:04 +08:00
< el-alert v-if = "reviewNavigationError" type="error" :closable="false" show-icon class="review-navigation-error" >
< template # title >{{ reviewNavigationError . message }}</ template >
< ApiErrorMeta :details = "reviewNavigationError" />
< el-button v-if = "selectedInbound?.inboundCode || selectedInbound?.orderId" link type="danger" :loading="reviewNavigationLoading" @click="handleTaskChanged" > 重新打开订单详情 </ el -button >
</ el-alert >
2026-08-13 11:57:44 +08:00
< MeasurementReviewPanel
2026-08-17 17:49:10 +08:00
v-if = "selectedInbound"
2026-10-20 10:11:31 +08:00
ref = "reviewPanelRef"
:show-actions = "false"
2026-08-13 17:14:51 +08:00
:tenant-id = "selectedInbound.tenantId"
2026-08-13 11:57:44 +08:00
:inbound-id = "selectedInbound.id"
2026-09-09 11:50:42 +08:00
:fulfillment-type = "selectedInbound.fulfillmentType"
2026-08-18 11:41:14 +08:00
: customer -name =" selectedInbound.customerName || selectedInbound.companyName "
:channel-name = "selectedInbound.channelName"
:channel-code = "selectedInbound.channelCode"
2026-09-01 20:49:04 +08:00
:post-approve-loading = "reviewNavigationLoading"
:approval-completed = "reviewApproved"
2026-08-13 11:57:44 +08:00
@changed ="handleTaskChanged"
/>
2026-10-20 10:11:31 +08:00
< template # footer = "{ close }" >
< div class = "review-dialog-footer" >
< el-button :disabled = "reviewNavigationLoading" @click ="close" > 关闭 </ el -button >
< template v-if = "reviewPanelRef?.isPending && reviewPanelRef?.canReview" >
< el-button type = "primary" plain @click ="reviewPanelRef?.enableWeightCorrection()" > 修改重量 </ el -button >
< el-button type = "primary" : loading = "reviewPanelRef?.actionLoading === 'approve' || reviewNavigationLoading" :disabled = "reviewApproved" @click ="reviewPanelRef?.confirmApprove()" >{{ reviewApproved ? ' 审核已通过 ' : reviewPanelRef ? .approvalButtonLabel }} </ el -button >
</ template >
</ div >
</ template >
</ AppDialog >
2026-08-17 17:49:10 +08:00
< InboundMeasurementDialog
v-if = "!isReview && selectedInbound"
v-model = "measurementDialogOpen"
:customer-id = "selectedInbound.tenantId"
2026-09-18 11:41:27 +08:00
:customer-name = "customerDisplayName(selectedInbound)"
2026-08-17 17:49:10 +08:00
:inbound-id = "selectedInbound.id"
:inbound-code = "selectedInbound.inboundCode"
@updated ="handleMeasurementUpdated"
@submitted ="handleMeasurementSubmitted"
@closed ="clearSelectedInbound"
/>
2026-08-13 11:57:44 +08:00
</ AdminPage >
</ template >
< style scoped >
2026-10-20 10:11:31 +08:00
. review - dialog - footer { display : flex ; align - items : center ; justify - content : flex - end ; flex - wrap : wrap ; gap : var ( -- admin - space - 4 , 10 px ); }
. review - dialog - footer : deep (. el - button ) { margin - left : 0 ; }
2026-08-13 11:57:44 +08:00
. inbound - task - page { min - width : 0 ; }
2026-09-14 17:54:21 +08:00
. state - stack { display : grid ; gap : var ( -- admin - space - 5 , 12 px ); }
2026-09-01 20:49:04 +08:00
. review - navigation - error { margin - bottom : 14 px ; }
2026-09-14 17:54:21 +08:00
. row - actions { white - space : nowrap ; }
2026-08-13 11:57:44 +08:00
. primary - cell { display : grid ; gap : 3 px ; min - width : 0 ; }
. primary - cell strong , . primary - cell span { overflow - wrap : anywhere ; }
2026-08-30 17:43:03 +08:00
. inbound - code - cell { display : grid ; gap : 3 px ; min - width : 0 ; }
2026-09-14 17:54:21 +08:00
. primary - cell span , . muted - action { color : var ( -- cc - muted ); font - size : var ( -- admin - font - caption , 12 px ); }
2026-08-13 11:57:44 +08:00
@ media ( max - width : 520 px ) {
. row - actions { align - items : stretch ; flex - direction : column ; }
. row - actions : deep (. el - button ) { width : 100 % ; }
}
</ style >
< style >
2026-09-14 15:33:35 +08:00
. inbound - task - dialog . admin - density - workspace . el - dialog : not (. is - fullscreen ) { height : auto ; max - height : 92 vh ; overflow : hidden ; }
2026-08-13 11:57:44 +08:00
@ media ( max - width : 520 px ) {
. inbound - task - dialog . el - dialog__body { padding : 14 px ; }
}
</ style >