@@ -14,31 +14,95 @@ import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
import { getApiErrorDetails , type ApiErrorDetails } from '@/shared/api/http'
import { getApiErrorDetails , type ApiErrorDetails } from '@/shared/api/http'
import { cancelOutbound , createOneClickDropshipOutbound , createReturnOutbound , listReturnOutboundCandidates , listOutbounds , type OutboundRow , type ReturnOutboundCandidate } from '../api/outbounds'
import { cancelOutbound , createOneClickDropshipOutbound , createReturnOutbound , listOneClickOutboundCandidates , listReturnOutboundCandidates , listOutbounds , type OneClickOutboundCandidate , type OutboundRow , type ReturnOutboundCandidate } from '../api/outbounds'
import { listInventoryBalances , listInventoryPositions , type InventoryBalanceDto , type InventoryPositionDto } from '../api/inventory'
import { type InventoryBalanceDto , type InventoryPositionDto } from '../api/inventory'
import { formatOverseasDateTime } from '../lastMilePresentation'
import { formatOverseasDateTime } from '../lastMilePresentation'
import { overseasWarehousePermissions as permission } from '../permissions'
import { overseasWarehousePermissions as permission } from '../permissions'
import { listPlatformTenants , type PlatformTenantResource } from '@/modules/iam/api/platformIam'
import { listPlatformWarehouses , type PlatformWarehouseSummaryResource } from '@/modules/warehouses'
import { getReturnCarton , listReturnCartons , listReturnStock , type ReturnCartonDetail , type ReturnCartonRow , type ReturnStockRow } from '../api/returnInventory'
const router = useRouter ( ) ; const access = useAccessPreviewStore ( )
const router = useRouter ( ) ; const access = useAccessPreviewStore ( )
const rows = ref < OutboundRow [ ] > ( [ ] ) ; const page = ref ( 1 ) ; const pageSize = ref ( 20 ) ; const total = ref ( 0 ) ; const loading = ref ( false ) ; const error = ref < ApiErrorDetails | null > ( null ) ; const mutationError = ref < ApiErrorDetails | null > ( null )
const rows = ref < OutboundRow [ ] > ( [ ] ) ; const page = ref ( 1 ) ; const pageSize = ref ( 20 ) ; const total = ref ( 0 ) ; const loading = ref ( false ) ; const error = ref < ApiErrorDetails | null > ( null ) ; const mutationError = ref < ApiErrorDetails | null > ( null )
const filters = ref < QueryModel > ( { documentNo : '' , lxCode : '' , warehouseId : '' , status : '' } ) ; const createOpen = ref ( false ) ; const saving = ref ( false ) ; const recipient = ref ( '' ) ; const remark = ref ( '' ) ; const quantity = ref < number | undefined > ( ) ; const cartonMarks = ref ( '' ) ; const selectedReturnMarks = ref < string [ ] > ( [ ] ) ; const selectedCustomerId = ref ( '' ) ; const selectedPositionId = ref ( '' ) ; const positionOptions = ref < Array < { balance : InventoryBalanceDto ; position : InventoryPositionDto } > > ( [ ] ) ; const returnCandidates = ref < ReturnOutboundCandidate [ ] > ( [ ] ) ; const selectedWarehouseId = ref ( '' ) ; const selectedReturnNo = ref ( '' ) ; const outboundType = ref < 'ONE_CLICK_DROPSHIP' | 'RETURN_OUTBOUND' > ( 'ONE_CLICK_DROPSHIP' ) ; const inventoryLoading = ref ( false )
const filters = ref < QueryModel > ( { documentNo : '' , lxCode : '' , warehouseId : '' , status : '' , outboundType : '' } ) ; const createOpen = ref ( false ) ; const saving = ref ( false ) ; const recipient = ref ( '' ) ; const remark = ref ( '' ) ; const quantity = ref < number | undefined > ( ) ; const cartonMarks = ref ( '' ) ; const selectedReturnMarks = ref < string [ ] > ( [ ] ) ; const selectedCustomerId = ref ( '' ) ; const selectedPositionId = ref ( '' ) ; const positionOptions = ref < Array < { balance : InventoryBalanceDto ; position : InventoryPositionDto } > > ( [ ] ) ; const dropshipCandidates = ref < OneClickOutboundCandidate [ ] > ( [ ] ) ; const selectedDropshipIds = ref < string [ ] > ( [ ] ) ; const returnCandidates = ref < ReturnOutboundCandidate [ ] > ( [ ] ) ; const selectedWarehouseId = ref ( '' ) ; const selectedReturnNo = ref ( '' ) ; const outboundType = ref < 'ONE_CLICK_DROPSHIP' | 'RETURN_OUTBOUND' > ( 'ONE_CLICK_DROPSHIP' ) ; const inventoryLoading = ref ( false ) ; const dropshipQueryInput = ref ( '' ) ; const dropshipQuery = ref ( '' ) ; const dropshipPage = ref ( 1 ) ; const dropshipPageSize = ref ( 5 )
const tenantOptions = ref < PlatformTenantResource [ ] > ( [ ] )
const warehouseOptionsSource = ref < PlatformWarehouseSummaryResource [ ] > ( [ ] )
const returnStockRows = ref < ReturnStockRow [ ] > ( [ ] )
const returnCartonRows = ref < ReturnCartonRow [ ] > ( [ ] )
const selectedReturnSkus = ref < string [ ] > ( [ ] )
const returnSkuQueryInput = ref ( '' ) ; const returnSkuQuery = ref ( '' )
const returnCartonQueryInput = ref ( '' ) ; const returnCartonQuery = ref ( '' )
const returnSkuPage = ref ( 1 ) ; const returnCartonPage = ref ( 1 )
const returnSkuPageSize = ref ( 5 ) ; const returnCartonPageSize = ref ( 5 )
const returnCartonDetails = ref < Record < string , ReturnCartonDetail > > ( { } )
const customerOptions = computed ( ( ) => {
const customerOptions = computed ( ( ) => {
const customers = new Map < string , string > ( )
const customers = new Map < string , string > ( )
tenantOptions . value . forEach ( ( item ) => {
if ( item . status !== 'ACTIVE' ) return
const name = item . tenantName ? . trim ( ) || '客户名称待补充'
const phone = item . contactPhone ? . trim ( )
customers . set ( String ( item . id ) , phone ? ` ${ name } - ${ phone } ` : name )
} )
positionOptions . value . forEach ( ( { balance } ) => customers . set ( balance . customerId , balance . customerName ? . trim ( ) || '客户名称待补充' ) )
positionOptions . value . forEach ( ( { balance } ) => customers . set ( balance . customerId , balance . customerName ? . trim ( ) || '客户名称待补充' ) )
return [ ... customers ] . map ( ( [ value , label ] ) => ( { value , label } ) )
return [ ... customers ] . map ( ( [ value , label ] ) => ( { value , label } ) )
} )
} )
const availablePositionOptions = computed ( ( ) => positionOptions . value . filter ( ( { balance } ) => balance . customerId === selectedCustomerId . value ) )
const availablePositionOptions = computed ( ( ) => positionOptions . value . filter ( ( { balance } ) => balance . customerId === selectedCustomerId . value ) )
const warehouseOptions = computed ( ( ) => [ ... new Map ( positionOptions . value . filter ( ( { balance } ) => balance . customerId === selectedCustomerId . value ) . map ( ( { balance } ) => [ balance . overseasWarehouseId , balance . overseasWarehouseName || balance . overseasWarehouseId ] ) ) . entries ( ) ] . map ( ( [ value , label ] ) => ( { value , label : label || value } ) ) )
const warehouseOptions = computed ( ( ) => warehouseOptionsSource . value . filter ( ( item ) => item . status === 'ACTIVE' && item . warehouseType === 'OVERSEAS' ) . map ( ( item ) => ( { value : String ( item . id ) , label : item . warehouseName || String ( item . id ) } ) ) )
const selectedReturnStock = computed ( ( ) => returnStockRows . value . filter ( ( item ) => Number ( item . availableQuantity || 0 ) > 0 ) )
const selectedReturnCartons = computed ( ( ) => returnCartonRows . value . filter ( ( item ) => item . status === 'AVAILABLE' ) )
const queryTokens = ( value : string ) => value . split ( /[,, \n]+/ ) . map ( ( item ) => item . replace ( /\s+/g , '' ) . toLowerCase ( ) ) . filter ( Boolean )
const matchesAnyToken = ( value : string , query : string ) => { const tokens = queryTokens ( query ) ; const normalized = value . replace ( /\s+/g , '' ) . toLowerCase ( ) ; return ! tokens . length || tokens . some ( ( token ) => normalized . includes ( token ) ) }
const filteredReturnStocks = computed ( ( ) => selectedReturnStock . value . filter ( ( item ) => matchesAnyToken ( ` ${ item . skuCode } , ${ item . productNameZh || '' } ` , returnSkuQuery . value ) ) )
const filteredReturnCartons = computed ( ( ) => selectedReturnCartons . value . filter ( ( item ) => {
const detailSkus = returnCartonDetails . value [ item . cartonMark ] ? . items ? . map ( ( detail ) => detail . skuCode ) . join ( ',' ) || ''
return matchesAnyToken ( ` ${ item . cartonMark } , ${ item . fbaCartonMark || '' } , ${ detailSkus } ` , returnCartonQuery . value )
} ) )
const pagedReturnStocks = computed ( ( ) => filteredReturnStocks . value . slice ( ( returnSkuPage . value - 1 ) * returnSkuPageSize . value , returnSkuPage . value * returnSkuPageSize . value ) )
const pagedReturnCartons = computed ( ( ) => filteredReturnCartons . value . slice ( ( returnCartonPage . value - 1 ) * returnCartonPageSize . value , returnCartonPage . value * returnCartonPageSize . value ) )
const effectiveReturnMarks = computed ( ( ) => [ ... new Set ( selectedReturnMarks . value ) ] . filter ( Boolean ) as string [ ] )
const returnMarksModel = computed ( { get : ( ) => selectedReturnMarks . value , set : ( value : string [ ] ) => { selectedReturnMarks . value = value } } )
const returnMarksModel = computed ( { get : ( ) => selectedReturnMarks . value , set : ( value : string [ ] ) => { selectedReturnMarks . value = value } } )
const filteredDropshipCandidates = computed ( ( ) => dropshipCandidates . value . filter ( item => ! dropshipQuery . value . trim ( ) || [ item . orderNo , item . inboundCode , item . customerName , item . palletNo , item . skuCode , item . productName ] . join ( ' ' ) . toLowerCase ( ) . includes ( dropshipQuery . value . trim ( ) . toLowerCase ( ) ) ) )
const pagedDropshipCandidates = computed ( ( ) => filteredDropshipCandidates . value . slice ( ( dropshipPage . value - 1 ) * dropshipPageSize . value , dropshipPage . value * dropshipPageSize . value ) )
const oneClickMarks = computed ( ( ) => typeof cartonMarks . value === 'string' ? cartonMarks . value . split ( /[,\n, ]+/ ) . map ( value => value . trim ( ) ) . filter ( Boolean ) : [ ] )
const oneClickMarks = computed ( ( ) => typeof cartonMarks . value === 'string' ? cartonMarks . value . split ( /[,\n, ]+/ ) . map ( value => value . trim ( ) ) . filter ( Boolean ) : [ ] )
const columns : DataTableColumn [ ] = [ { key : 'actions' , label : '操作' , width : 180 , fixed : 'left' , slotName : 'actions' } , { key : 'outboundNo' , prop : 'outboundNo' , label : '出库单号' , minWidth : 180 } , { key : 'lxCodes' , prop : 'lxCodes' , label : 'LX代码' , minWidth : 180 } , { key : 'dispatchNo' , prop : 'dispatchNo' , label : '关联排车单' , minWidth : 160 } , { key : 'status' , prop : 'status' , label : '状态' , width : 130 , slotName : 'status' } , { key : 'palletCount' , prop : 'palletCount' , label : '托盘数' , width : 100 } , { key : 'packageCount' , prop : 'packageCount' , label : '箱数' , width : 100 } , { key : 'createdAt' , prop : 'createdAt' , label : '创建时间' , minWidth : 180 } ]
const columns : DataTableColumn [ ] = [ { key : 'actions' , label : '操作' , width : 180 , fixed : 'left' , slotName : 'actions' } , { key : 'outboundNo' , prop : 'outboundNo' , label : '出库单号' , minWidth : 180 } , { key : 'outboundType' , prop : 'outboundType' , label : '出库类型' , width : 150 , slotName : 'outboundType' } , { key : 'lxCodes' , prop : 'lxCodes' , label : 'LX代码' , minWidth : 180 } , { key : 'dispatchNo' , prop : 'dispatchNo' , label : '关联排车单' , minWidth : 160 } , { key : 'status' , prop : 'status' , label : '状态' , width : 130 , slotName : 'status' } , { key : 'palletCount' , prop : 'palletCount' , label : '托盘数' , width : 100 } , { key : 'packageCount' , prop : 'packageCount' , label : '箱数' , width : 100 } , { key : 'createdAt' , prop : 'createdAt' , label : '创建时间' , minWidth : 180 } ]
const fields : QueryField [ ] = [ { key : 'documentNo' , type : 'input' , label : '出库单号' , placeholder : '请输入出库单号' , width : 220 } , { key : 'lxCode' , type : 'input' , label : 'LX代码' , placeholder : '请输入领星入库单代码' , width : 220 } , { key : 'warehouseId' , type : 'input' , label : '目的仓' , placeholder : '请输入目的仓' , width : 220 } , { key : 'status' , type : 'select' , label : '状态' , placeholder : '全部状态' , width : 160 , options : [ { label : '全部' , value : '' } , { label : '待出库' , value : 'WAIT_OUTBOUND' } , { label : '已出库' , value : 'OUTBOUNDED' } ] } ]
const fields : QueryField [ ] = [ { key : 'documentNo' , type : 'input' , label : '出库单号' , placeholder : '请输入出库单号' , width : 220 } , { key : 'lxCode' , type : 'input' , label : 'LX代码' , placeholder : '请输入领星入库单代码' , width : 220 } , { key : 'warehouseId' , type : 'input' , label : '目的仓' , placeholder : '请输入目的仓' , width : 220 } , { key : 'status' , type : 'select' , label : '状态' , placeholder : '全部状态' , width : 160 , options : [ { label : '全部' , value : '' } , { label : '待出库' , value : 'WAIT_OUTBOUND' } , { label : '已出库' , value : 'OUTBOUNDED' } ] } , { key : 'outboundType' , type : 'select' , label : '出库类型' , placeholder : '全部出库类型' , width : 170 , options : [ { label : '全部' , value : '' } , { label : '一件代发出库' , value : 'ONE_CLICK_DROPSHIP' } , { label : '退货出库' , value : 'RETURN_OUTBOUND' } , { label : '派送出库' , value : 'DISPATCH_DELIVERY' } ] } ]
async function load ( model : QueryModel = filters . value ) { Object . assign ( filters . value , model ) ; loading . value = true ; error . value = null ; try { const result = await listOutbounds ( { page : page . value , size : pageSize . value , documentNo : String ( filters . value . documentNo || '' ) . trim ( ) || undefined , lxCode : String ( filters . value . lxCode || '' ) . trim ( ) || undefined , warehouseId : String ( filters . value . warehouseId || '' ) . trim ( ) || undefined , status : String ( filters . value . status || '' ) . trim ( ) || undefined } ) ; rows . value = result . records . map ( ( row ) => ( { ... row , createdAt : formatOverseasDateTime ( row . createdAt ) } ) ) ; total . value = result . total } catch ( cause ) { error . value = getApiErrorDetails ( cause , '出库单列表加载失败' ) } finally { loading . value = false } }
async function load ( model : QueryModel = filters . value ) { Object . assign ( filters . value , model ) ; loading . value = true ; error . value = null ; try { const result = await listOutbounds ( { page : page . value , size : pageSize . value , documentNo : String ( filters . value . documentNo || '' ) . trim ( ) || undefined , lxCode : String ( filters . value . lxCode || '' ) . trim ( ) || undefined , warehouseId : String ( filters . value . warehouseId || '' ) . trim ( ) || undefined , status : String ( filters . value . status || '' ) . trim ( ) || undefined , outboundType : String ( filters . value . outboundType || '' ) . trim ( ) || undefined } ) ; rows . value = result . records . map ( ( row ) => ( { ... row , createdAt : formatOverseasDateTime ( row . createdAt ) } ) ) ; total . value = result . total } catch ( cause ) { error . value = getApiErrorDetails ( cause , '出库单列表加载失败' ) } finally { loading . value = false } }
async function openCreate ( ) { createOpen . value = true ; outboundType . value = 'ONE_CLICK_DROPSHIP' ; selectedCustomerId . value = '' ; selectedPositionId . value = '' ; selectedWarehouseId . value = '' ; selectedReturnNo . value = '' ; returnCandidates . value = [ ] ; selectedReturnMarks . value = [ ] ; quantity . value = undefined ; cartonMarks . value = '' ; recipient . value = '' ; remark . value = '' ; inventoryLoading . value = true ; try { const balances = await listInventoryBalances ( { page : 1 , size : 200 , inventoryStatus : 'AVAILABLE' } ) ; const groups = await Promise . all ( balances . records . map ( async balance => ( await listInventoryPositions ( balance . id , { inventoryStatus : 'AVAILABLE' } ) ) . map ( position => ( { balance , position } ) ) ) ) ; positionOptions . value = groups . flat ( ) . filter ( item => item . position . availablePackages > 0 ) } catch ( cause ) { ElMessage . error ( getApiErrorDetails ( cause , '可用库存加载失败' ) . message ) } finally { inventoryLoading . value = false } }
function outboundTypeLabel ( value ? : string | null ) { return value == = 'ONE_CLICK_DROPSHIP' ? '一件代发出库 ' : value === 'RETURN_OUTBOUND' ? '退货出库 ' : value === 'DISPATCH_DELIVERY' ? '派送出库' : value || '—' }
function selectCustomer ( ) { selectedPositionId . value = '' ; quantity . value = undefined ; selectedWarehouseId . value = '' ; selectedReturnNo . value = '' ; selectedReturnMarks . value = [ ] ; returnCandidates . value = [ ] }
async function loadInventoryOptions ( ) {
async function loadReturnCandidates ( ) { if ( ! selectedCustomerId . value || ! selectedWarehouseId . value ) return ; inventoryLoading . value = true ; try { returnCandidates . value = await listReturnOutboundCandidates ( selectedCustomerId . value , selectedWarehouseId . value ) } catch ( cause ) { ElMessage . error ( getApiErrorDetails ( cause , '退货箱唛加载失败' ) . message ) } finally { inventoryLoading . value = false } }
inventoryLoading . value = true
async function submit ( ) { const marks = String ( cartonMarks . value ) . split ( /[,\n, ]+/ ) . map ( value => value . trim ( ) ) . filter ( Boolean ) ; if ( ! selectedCustomerId . value || ! recipient . value . trim ( ) ) { ElMessage . warning ( '请选择客户并填写收件信息' ) ; return } ; saving . value = true ; try { if ( outboundType . value === 'RETURN_OUTBOUND' ) { if ( ! selectedWarehouseId . value || ! selectedReturnNo . value || ! selectedReturnMarks . value . length ) { ElMessage . warning ( '请选择海外仓、退货单和至少一个箱唛' ) ; return } ; await createReturnOutbound ( selectedCustomerId . value , { recipient : recipient . value . trim ( ) , overseasWarehouseId : selectedWarehouseId . value , returnNo : selectedReturnNo . value , cartonMarks : selectedReturnMarks . value , remark : remark . value . trim ( ) || undefined , idempotencyKey : ` web-return- ${ Date . now ( ) } ` } ) ; ElMessage . success ( '退货出库单已创建并推送PDA箱唛拣货任务' ) } else { const selected = availablePositionOptions . value . find ( item => item . position . id === selectedPositionId . value ) ; if ( ! selected || ! quantity . value || quantity . value < 1 ) { ElMessage . warning ( '请选择托盘和出库数量' ) ; return } ; if ( quantity . value > selected . position . availablePackages ) { ElMessage . warning ( '出库数量不能超过可用库存' ) ; return } ; if ( marks . length && marks . length !== quantity . value ) { ElMessage . warning ( '指定箱唛数量必须等于出库数量' ) ; return } ; await createOneClickDropshipOutbound ( selectedCustomerId . value , { recipient : recipient . value . trim ( ) , items : [ { inventoryPositionId : selected . position . id , packages : quantity . value , sku : selected . balance . skuCode || undefined , cartonMarks : marks . length ? marks : undefined } ] , remark : remark . value . trim ( ) || undefined , idempotencyKey : ` web-dropship- ${ Date . now ( ) } ` } ) ; ElMessage . success ( '一件代发出库单已创建并推送PDA拣货任务' ) } createOpen . value = false ; await load ( ) } catch ( cause ) { ElMessage . error ( getApiErrorDetails ( cause , '出库单创建失败' ) . message ) } finally { saving . value = false } }
try {
const [ tenants , warehouses ] = await Promise . allSettled ( [
listPlatformTenants ( { page : 1 , size : 200 , status : 'ACTIVE' } ) ,
listPlatformWarehouses ( { page : 1 , size : 200 , warehouseType : 'OVERSEAS' , status : 'ACTIVE' } ) ,
] )
if ( tenants . status === 'fulfilled' ) tenantOptions . value = tenants . value . records
else ElMessage . error ( getApiErrorDetails ( tenants . reason , '客户列表加载失败' ) . message )
if ( warehouses . status === 'fulfilled' ) {
warehouseOptionsSource . value = warehouses . value . records
if ( ! selectedWarehouseId . value ) {
const lax = warehouses . value . records . find ( ( item ) => item . warehouseCode === 'US-LAX-01' || item . warehouseName ? . includes ( '洛杉矶' ) )
selectedWarehouseId . value = String ( lax ? . id || warehouses . value . records [ 0 ] ? . id || '' )
}
} else ElMessage . error ( getApiErrorDetails ( warehouses . reason , '海外仓列表加载失败' ) . message )
} finally { inventoryLoading . value = false }
}
async function openCreate ( ) { createOpen . value = true ; outboundType . value = 'ONE_CLICK_DROPSHIP' ; selectedCustomerId . value = '' ; selectedWarehouseId . value = '' ; selectedDropshipIds . value = [ ] ; dropshipCandidates . value = [ ] ; dropshipPage . value = 1 ; returnSkuQueryInput . value = '' ; returnSkuQuery . value = '' ; returnCartonQueryInput . value = '' ; returnCartonQuery . value = '' ; returnSkuPage . value = 1 ; returnCartonPage . value = 1 ; await loadInventoryOptions ( ) ; await loadDropshipCandidates ( ) }
async function loadDropshipCandidates ( ) { if ( ! selectedWarehouseId . value ) { dropshipCandidates . value = [ ] ; return } ; dropshipPage . value = 1 ; inventoryLoading . value = true ; try { dropshipCandidates . value = await listOneClickOutboundCandidates ( selectedCustomerId . value || undefined , { warehouseId : selectedWarehouseId . value } ) } catch ( cause ) { dropshipCandidates . value = [ ] ; ElMessage . error ( getApiErrorDetails ( cause , '一件代发订单加载失败' ) . message ) } finally { inventoryLoading . value = false } }
async function changeDropshipWarehouse ( ) { selectedDropshipIds . value = [ ] ; await loadDropshipCandidates ( ) }
async function selectCustomer ( ) { selectedPositionId . value = '' ; selectedDropshipIds . value = [ ] ; selectedReturnNo . value = '' ; selectedReturnMarks . value = [ ] ; selectedReturnSkus . value = [ ] ; returnCandidates . value = [ ] ; returnStockRows . value = [ ] ; returnCartonRows . value = [ ] ; returnCartonDetails . value = { } ; dropshipCandidates . value = [ ] ; dropshipPage . value = 1 ; if ( outboundType . value === 'RETURN_OUTBOUND' ) { if ( selectedCustomerId . value && selectedWarehouseId . value ) await loadReturnCandidates ( ) ; return } ; await loadDropshipCandidates ( ) }
async function changeOutboundType ( ) { selectedReturnNo . value = '' ; selectedReturnMarks . value = [ ] ; selectedReturnSkus . value = [ ] ; returnCandidates . value = [ ] ; returnStockRows . value = [ ] ; returnCartonRows . value = [ ] ; returnCartonDetails . value = { } ; selectedDropshipIds . value = [ ] ; dropshipCandidates . value = [ ] ; if ( outboundType . value === 'ONE_CLICK_DROPSHIP' ) await loadDropshipCandidates ( ) }
async function loadReturnCandidates ( ) { if ( ! selectedCustomerId . value || ! selectedWarehouseId . value ) return ; inventoryLoading . value = true ; try { const [ candidates , stock , cartons ] = await Promise . all ( [ listReturnOutboundCandidates ( selectedCustomerId . value , selectedWarehouseId . value ) , listReturnStock ( { page : 1 , size : 200 , customerId : selectedCustomerId . value , overseasWarehouseId : selectedWarehouseId . value , status : 'AVAILABLE' } ) , listReturnCartons ( { page : 1 , size : 200 , customerId : selectedCustomerId . value , overseasWarehouseId : selectedWarehouseId . value , status : 'AVAILABLE' } ) ] ) ; returnCandidates . value = candidates ; returnStockRows . value = stock . records ; returnCartonRows . value = cartons . records ; const details = await Promise . all ( cartons . records . map ( async ( row ) => [ row . cartonMark , await getReturnCarton ( row . cartonMark ) ] as const ) ) ; returnCartonDetails . value = Object . fromEntries ( details ) ; selectedReturnSkus . value = [ ] ; selectedReturnMarks . value = [ ] } catch ( cause ) { ElMessage . error ( getApiErrorDetails ( cause , '客户库存加载失败' ) . message ) } finally { inventoryLoading . value = false } }
function dropshipCandidateKey ( item : OneClickOutboundCandidate ) { return ` ${ item . inventoryPositionId } : ${ item . skuCode || '' } ` }
function isDropshipSelected ( item : OneClickOutboundCandidate ) { return selectedDropshipIds . value . includes ( dropshipCandidateKey ( item ) ) }
function toggleDropshipCandidate ( item : OneClickOutboundCandidate , checked : boolean | string | number ) { const key = dropshipCandidateKey ( item ) ; selectedDropshipIds . value = checked ? [ ... new Set ( [ ... selectedDropshipIds . value , key ] ) ] : selectedDropshipIds . value . filter ( value => value !== key ) }
function searchDropshipCandidates ( ) { dropshipQuery . value = dropshipQueryInput . value . trim ( ) ; dropshipPage . value = 1 }
function resetDropshipCandidates ( ) { dropshipQueryInput . value = '' ; dropshipQuery . value = '' ; dropshipPage . value = 1 }
function normalizeBatchQuery ( value : string ) { return value . split ( /[,, \n]+/ ) . map ( ( item ) => item . replace ( /\s+/g , '' ) ) . filter ( Boolean ) . join ( ',' ) }
function searchReturnSkus ( ) { returnSkuQueryInput . value = normalizeBatchQuery ( returnSkuQueryInput . value ) ; returnSkuQuery . value = returnSkuQueryInput . value ; returnSkuPage . value = 1 }
function searchReturnCartons ( ) { returnCartonQueryInput . value = normalizeBatchQuery ( returnCartonQueryInput . value ) ; returnCartonQuery . value = returnCartonQueryInput . value ; returnCartonPage . value = 1 }
function toggleReturnSku ( sku : string , checked : boolean | string | number ) { selectedReturnSkus . value = checked ? [ ... new Set ( [ ... selectedReturnSkus . value , sku ] ) ] : selectedReturnSkus . value . filter ( ( value ) => value !== sku ) }
async function submit ( ) { const marks = String ( cartonMarks . value ) . split ( /[,\n, ]+/ ) . map ( value => value . trim ( ) ) . filter ( Boolean ) ; if ( ! selectedWarehouseId . value ) { ElMessage . warning ( '请选择海外仓' ) ; return } ; if ( outboundType . value === 'RETURN_OUTBOUND' && ! selectedCustomerId . value ) { ElMessage . warning ( '请选择客户' ) ; return } ; saving . value = true ; try { if ( outboundType . value === 'RETURN_OUTBOUND' ) { if ( ! recipient . value . trim ( ) || ! selectedReturnNo . value || ! selectedReturnMarks . value . length ) { ElMessage . warning ( '请填写收件信息并选择退货单和箱唛' ) ; return } ; await createReturnOutbound ( selectedCustomerId . value , { recipient : recipient . value . trim ( ) , overseasWarehouseId : selectedWarehouseId . value , returnNo : selectedReturnNo . value , cartonMarks : selectedReturnMarks . value , remark : remark . value . trim ( ) || undefined , idempotencyKey : ` web-return- ${ Date . now ( ) } ` } ) ; ElMessage . success ( '退货出库单已创建并推送PDA箱唛拣货任务' ) } else { const selected = dropshipCandidates . value . filter ( item => selectedDropshipIds . value . includes ( dropshipCandidateKey ( item ) ) ) ; if ( ! selected . length ) { ElMessage . warning ( '请至少选择一个已完成入库的一件代发订单' ) ; return } ; const customerIds = new Set ( selected . map ( item => item . customerId ) . filter ( Boolean ) ) ; if ( customerIds . size !== 1 ) { ElMessage . warning ( '一张出库单只能选择同一个客户的订单' ) ; return } ; const warehouses = new Set ( selected . map ( item => item . overseasWarehouseId ) . filter ( Boolean ) ) ; if ( warehouses . size > 1 || ( warehouses . size === 1 && ! warehouses . has ( selectedWarehouseId . value ) ) ) { ElMessage . warning ( '所选订单必须属于当前海外仓' ) ; return } ; const items = selected . map ( item => ( { inventoryPositionId : item . inventoryPositionId , packages : item . availablePackages , sku : item . skuCode || '' , cartonMarks : [ ] as string [ ] } ) ) ; await createOneClickDropshipOutbound ( [ ... customerIds ] [ 0 ] || '' , { recipient : '一件代发订单直出' , items , remark : remark . value . trim ( ) || undefined , idempotencyKey : ` web-dropship- ${ Date . now ( ) } ` } ) ; ElMessage . success ( ` 已按订单生成 ${ selected . length } 个托盘出库明细并推送PDA拣货任务 ` ) } createOpen . value = false ; await load ( ) } catch ( cause ) { ElMessage . error ( getApiErrorDetails ( cause , '出库单创建失败' ) . message ) } finally { saving . value = false } }
function openDetail ( row : OutboundRow ) { void router . push ( { name : 'overseas-outbound-detail' , params : { outboundNo : row . outboundNo } } ) }
function openDetail ( row : OutboundRow ) { void router . push ( { name : 'overseas-outbound-detail' , params : { outboundNo : row . outboundNo } } ) }
function canCancel ( row : OutboundRow ) { return access . can ( permission . outboundCancel ) && row . status === 'PICKED' }
function canCancel ( row : OutboundRow ) { return access . can ( permission . outboundCancel ) && row . status === 'PICKED' }
async function cancel ( row : OutboundRow ) { if ( ! canCancel ( row ) ) return ; let reason = '' ; try { const result = await ElMessageBox . prompt ( ` 出库单 ${ row . outboundNo } · 出库数量 ${ row . packageCount ? ? 0 } ` , '取消出库单' , { inputType : 'textarea' , inputPlaceholder : '请输入取消原因' , inputPattern : /\S+/ , inputErrorMessage : '请输入取消原因' , confirmButtonText : '确认取消' , cancelButtonText : '返回' , type : 'warning' } ) ; reason = result . value . trim ( ) } catch { return } ; mutationError . value = null ; try { await cancelOutbound ( row . outboundNo , reason ) ; ElMessage . success ( '出库单已取消,库存及关联单据已回滚' ) ; await load ( ) } catch ( cause ) { mutationError . value = getApiErrorDetails ( cause , '取消出库单失败' ) } }
async function cancel ( row : OutboundRow ) { if ( ! canCancel ( row ) ) return ; let reason = '' ; try { const result = await ElMessageBox . prompt ( ` 出库单 ${ row . outboundNo } · 出库数量 ${ row . packageCount ? ? 0 } ` , '取消出库单' , { inputType : 'textarea' , inputPlaceholder : '请输入取消原因' , inputPattern : /\S+/ , inputErrorMessage : '请输入取消原因' , confirmButtonText : '确认取消' , cancelButtonText : '返回' , type : 'warning' } ) ; reason = result . value . trim ( ) } catch { return } ; mutationError . value = null ; try { await cancelOutbound ( row . outboundNo , reason ) ; ElMessage . success ( '出库单已取消,库存及关联单据已回滚' ) ; await load ( ) } catch ( cause ) { mutationError . value = getApiErrorDetails ( cause , '取消出库单失败' ) } }
@@ -54,12 +118,99 @@ onMounted(load)
< section v-if = "!error" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-outbounds" row-key="id" empty-text="暂无出库单" @update:page="load()" @update:page-size="page = 1; load()" >
< section v-if = "!error" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-outbounds" row-key="id" empty-text="暂无出库单" @update:page="load()" @update:page-size="page = 1; load()" >
< template # toolbar -actions > < el-button v-if = "access.can(permission.outboundCreate)" type="primary" @click="openCreate"><Plus :size="16" / > 新建出库单 < / el-button > < / template >
< template # toolbar -actions > < el-button v-if = "access.can(permission.outboundCreate)" type="primary" @click="openCreate"><Plus :size="16" / > 新建出库单 < / el-button > < / template >
< template # actions = "{ row }" > < el-button size = "small" type = "primary" plain @click ="openDetail(row as OutboundRow)" > < Eye :size = "15" / > 详情 < / el-button > < el-button v-if = "canCancel(row as OutboundRow)" size="small" type="danger" plain @click="cancel(row as OutboundRow)"><XCircle :size="15" / > 取消 < / el-button > < / template >
< template # actions = "{ row }" > < el-button size = "small" type = "primary" plain @click ="openDetail(row as OutboundRow)" > < Eye :size = "15" / > 详情 < / el-button > < el-button v-if = "canCancel(row as OutboundRow)" size="small" type="danger" plain @click="cancel(row as OutboundRow)"><XCircle :size="15" / > 取消 < / el-button > < / template >
< template # outboundType = "{ row }" > { { outboundTypeLabel ( row . outboundType ) } } < / template >
< template # status = "{ row }" > < StatusBadge : status = "row.status === 'CANCELLED' ? '已取消' : row.status === 'OUTBOUNDED' ? '已出库' : row.status === 'PICKED' ? '已拣货' : '拣货中'" / > < / template >
< template # status = "{ row }" > < StatusBadge : status = "row.status === 'CANCELLED' ? '已取消' : row.status === 'OUTBOUNDED' ? '已出库' : row.status === 'PICKED' ? '已拣货' : '拣货中'" / > < / template >
< / DataTable > < / section >
< / DataTable > < / section >
< / ListPageLayout > < / AdminPage >
< / ListPageLayout > < / AdminPage >
< AppDialog v-model = "createOpen" title="新建出库单" confirm-text="生成出库单并推送PDA" :confirm-loading="saving" @confirm="submit"><el-form class="create-outbound-form" label-position="top"><el-form-item label="出库类型" required><el-radio-group v-model="outboundType"><el-radio-button label="ONE_CLICK_DROPSHIP">一件代发出库</el-radio-button><el-radio-button label="RETURN_OUTBOUND">退货出库</el-radio-button></el-radio-group></el-form-item><el-form-item label="客户" required><el-select v-model="selectedCustomerId" :loading="inventoryLoading" filterable placeholder="请选择库存所属客户" @change="selectCustomer"><el-option v-for="item in customerOptions" :key="item.value" :value="item.value" :label="item.label" /></el-select></el-form-item><el-form-item label="收件信息" required><el-input v-model="recipient" placeholder="请输入收件人、电话和地址" /></el-form-item><template v-if="outboundType === 'ONE_CLICK_DROPSHIP'"><el-form-item label="出库托盘" required><el-select v-model="selectedPositionId" :loading="inventoryLoading" :disabled="!selectedCustomerId" filterable placeholder="请先选择客户,再选择有可用库存的托盘"><el-option v-for="item in availablePositionOptions" :key="item.position.id" :value="item.position.id" :label="`${item.position.palletNo || '托盘号待补充'} · ${item.balance.skuCode || '无SKU'} · 可用${item.position.availablePackages}`" /></el-select></el-form-item><el-form-item label="出库数量" required><el-input-number v-model="quantity" :min="1" placeholder="请输入出库数量" /></el-form-item><el-form-item label="指定箱唛(可选)"><el-input v-model="cartonMarks" type="textarea" :rows="3" placeholder="请输入箱唛,多个箱唛用逗号或换行分隔" /><div class="form-hint">填写后按指定箱唛出库,数量必须与出库数量一致。</div></el-form-item><el-alert title="一件代发按托盘维度作业,PDA完成拣货后扣减库存" type="info" :closable="false" /></template><template v-else><el-form-item label="海外仓" required><el-select v-model="selectedWarehouseId" :disabled="!selectedCustomerId" filterable placeholder="请选择海外仓" @change="loadReturnCandidates"><el-option v-for="item in warehouseOptions" :key="item.value" :value="item.value" :label="item.label" /></el-select></el-form-item><el-form-item label="尾程退货单" required><el-select v-model="selectedReturnNo" :disabled="!selectedWarehouseId" :loading="inventoryLoading" filterable placeholder="请选择有可用箱唛的退货单"><el-option v-for="item in [...new Set(returnCandidates.map(candidate => candidate.returnNo).filter(Boolean))]" :key="String(item ?? null)" :value="String(item ?? null)" :label="String(item ?? null)" /></el-select></el-form-item><el-form-item label="出库箱唛" required><el-select v-model="selectedReturnMarks" multiple :disabled="!selectedReturnNo" :loading="inventoryLoading" filterable collapse-tags placeholder="请选择要出库的箱唛"><el-option v-for="item in returnCandidates.filter(candidate => candidate.returnNo === selectedReturnNo)" :key="item.cartonMark || ''" :value="item.cartonMark || ''" :label="`${item.cartonMark} · ${item.fbaCartonMark || 'FBA箱唛待补充'} · ${item.destinationWarehouseCode || '目的仓待补充'}`" /></el-select></el-form-item><el-alert title="退货出库按尾程退货单的箱唛维度拣货,PDA扫描箱唛后完成出库" type="info" :closable="false" /></template><el-form-item label="备注"><el-input v-model="remark" type="textarea" placeholder="请输入备注(选填)" / > < / el-form-item > < / el-form > < / AppDialog >
< AppDialog v-model = "createOpen" title="新建出库单" width="min(1320px, calc(100vw - 32px))" confirm-text="生成出库单并推送PDA" :confirm-loading="saving" @confirm="submit" >
< el -form class = "create-outbound-form" label -position = " top " >
< el-form-item label = "出库类型" required >
< el-radio-group v-model = "outboundType" @change="changeOutboundType" >
< el -radio -button label = "ONE_CLICK_DROPSHIP" > 一件代发出库 < / el-radio-button >
< el-radio-button label = "RETURN_OUTBOUND" > 退货出库 < / el-radio-button >
< / el-radio-group >
< / el-form-item >
< el-form-item v-if = "outboundType === 'RETURN_OUTBOUND'" label="客户" required >
< el-select v-model = "selectedCustomerId" :loading="inventoryLoading" clearable filterable placeholder="请选择客户" @change="selectCustomer" >
< el -option v-for = "item in customerOptions" :key="item.value" :value="item.value" :label="item.label" / >
< / el-select >
< / el-form-item >
< template v-if = "outboundType !== 'RETURN_OUTBOUND'" >
< el -form -item label = "海外仓" required >
< el-select v-model = "selectedWarehouseId" filterable placeholder="请选择海外仓" @change="changeDropshipWarehouse" >
< el -option v-for = "item in warehouseOptions" :key="item.value" :value="item.value" :label="item.label" / >
< / el-select >
< / el-form-item >
< el-form-item label = "已完成入库的一件代发订单" >
< div class = "candidate-query" >
< el-input v-model = "dropshipQueryInput" clearable placeholder="按订单号、入库单号、客户、托盘号、SKU或品名筛选" @keyup.enter="searchDropshipCandidates" / >
< el-button type = "primary" :loading = "inventoryLoading" @click ="searchDropshipCandidates" > 查询 < / el -button >
< el-button @click ="resetDropshipCandidates" > 重置 < / el -button >
< / div >
< el-table v-loading = "inventoryLoading" :data="pagedDropshipCandidates" border stripe class="dropship-candidate-table" >
< el -table -column label = "选择" width = "68" align = "center" >
< template # default = "{ row }" > < el-checkbox : model -value = " isDropshipSelected ( row as OneClickOutboundCandidate ) " @change ="toggleDropshipCandidate(row as OneClickOutboundCandidate, $event)" / > < / template >
< / el-table-column >
< el-table-column prop = "customerName" label = "客户" min -width = " 150 " show -overflow -tooltip / >
< el-table-column prop = "orderNo" label = "尾程订单号" min -width = " 170 " show -overflow -tooltip / >
< el-table-column prop = "inboundCode" label = "入库单号" min -width = " 160 " show -overflow -tooltip / >
< el-table-column prop = "palletNo" label = "托盘号" min -width = " 140 " show -overflow -tooltip / >
< el-table-column prop = "skuCode" label = "SKU" min -width = " 140 " show -overflow -tooltip / >
< el-table-column prop = "productName" label = "品名" min -width = " 140 " show -overflow -tooltip / >
< el-table-column prop = "availablePackages" label = "可出箱数" width = "95" align = "right" / >
< / el-table >
< div v-if = "!inventoryLoading && !filteredDropshipCandidates.length" class="form-hint" > 暂无可直接出库的已完成入库订单 < / div >
< div v-if = "filteredDropshipCandidates.length" class="candidate-pagination" >
< span > 已选择 { { selectedDropshipIds . length } } 条 < / span >
< el-pagination v -model :current-page = "dropshipPage" v -model :page-size = "dropshipPageSize" : page -sizes = " [ 5 , 10 , 20 ] " :total = "filteredDropshipCandidates.length" layout = "total, sizes, prev, pager, next" / >
< / div >
< el-alert title = "一件代发按托盘与SKU选择;数量默认使用所选行全部可用箱数,同一张出库单只能选择同一海外仓。" type = "info" :closable = "false" / >
< / el-form-item >
< / template >
< template v-else >
< el -form -item label = "收件信息" required > < el-input v-model = "recipient" placeholder="请输入收件人、电话和地址" / > < / el-form-item >
< el-form-item label = "海外仓" required > < el-select v-model = "selectedWarehouseId" :disabled="!selectedCustomerId" filterable placeholder="请选择海外仓" @change="loadReturnCandidates"><el-option v-for="item in warehouseOptions" :key="item.value" :value="item.value" :label="item.label" / > < / el-select > < / el-form-item >
< div class = "return-lists" >
< section class = "return-list-card" >
< div class = "list-title" > < span > SKU库存 < / span > < small > 支持多个 SKU , 用英文逗号分隔 < / small > < / div >
< div class = "candidate-query" > < el-input v-model = "returnSkuQueryInput" clearable placeholder="如:SKU001,SKU002" /><el-button type="primary" @click="searchReturnSkus" > 查询 < / el -button > < / div >
< el-table :data = "pagedReturnStocks" border stripe class = "return-result-table" max -height = " 400 " > < el-table-column label = "选择" width = "60" align = "center" > < template # default = "{ row }" > < el-checkbox :model-value = "selectedReturnSkus.includes(row.skuCode)" @change ="toggleReturnSku(row.skuCode, $event)" / > < / template > < / el-table-column > < el-table-column prop = "skuCode" label = "SKU" min -width = " 150 " show -overflow -tooltip / > < el-table-column prop = "productNameZh" label = "品名" min -width = " 150 " show -overflow -tooltip / > < el-table-column prop = "availableQuantity" label = "可用数量" width = "100" align = "right" / > < / el-table >
< div class = "return-pagination" > < span > 已选择 { { selectedReturnSkus . length } } 个 SKU < / span > < el-pagination v -model :current-page = "returnSkuPage" v -model :page-size = "returnSkuPageSize" : page -sizes = " [ 5 , 10 , 20 , 50 ] " :total = "filteredReturnStocks.length" layout = "total, sizes, prev, pager, next" @ size -change = " returnSkuPage = 1 " / > < / div >
< / section >
< section class = "return-list-card" >
< div class = "list-title" > < span > 箱号列表 < / span > < small > 支持多个箱号或 SKU , 用英文逗号分隔 < / small > < / div >
< div class = "candidate-query" > < el-input v-model = "returnCartonQueryInput" clearable placeholder="如:RC001,RC002 或 SKU001,SKU002" /><el-button type="primary" @click="searchReturnCartons" > 查询 < / el -button > < / div >
< el-table :data = "pagedReturnCartons" border stripe class = "return-result-table" max -height = " 400 " > < el-table-column label = "选择" width = "60" align = "center" > < template # default = "{ row }" > < el-checkbox :model-value = "selectedReturnMarks.includes(row.cartonMark)" @change ="(v) => selectedReturnMarks = v ? [...new Set([...selectedReturnMarks, row.cartonMark])] : selectedReturnMarks.filter((m) => m !== row.cartonMark)" / > < / template > < / el-table-column > < el-table-column prop = "cartonMark" label = "箱号" min -width = " 190 " show -overflow -tooltip / > < el-table-column label = "箱内SKU及数量" min -width = " 230 " > < template # default = "{ row }" > { { returnCartonDetails [ row . cartonMark ] ? . items ? . map ( ( item ) => ` ${ item . skuCode } × ${ item . quantity } ` ) . join ( ', ' ) || '加载中' } } < / template > < / el-table-column > < / el-table >
< div class = "return-pagination" > < span > 已选择 { { selectedReturnMarks . length } } 个箱号 < / span > < el-pagination v -model :current-page = "returnCartonPage" v -model :page-size = "returnCartonPageSize" : page -sizes = " [ 5 , 10 , 20 , 50 ] " :total = "filteredReturnCartons.length" layout = "total, sizes, prev, pager, next" @ size -change = " returnCartonPage = 1 " / > < / div >
< / section >
< / div >
< el-alert title = "退货出库支持按客户选择单个或多个箱子;SKU数量直接从所选箱唛明细扣减。" type = "info" :closable = "false" / >
< / template >
< el-form-item label = "备注" > < el-input v-model = "remark" type="textarea" placeholder="请输入备注(选填)" / > < / el-form-item >
< / el-form >
< / AppDialog >
< / template >
< / template >
< style scoped >
< style scoped >
. create - outbound - form : deep ( . el - input ) , . create - outbound - form : deep ( . el - select ) , . create - outbound - form : deep ( . el - input - number ) { width : 100 % ; } . form - hint { margin - top : 6 px ; color : var ( -- cc - muted ) ; font - size : 12 px ; }
. create - outbound - form { padding : 0 4 px ; }
. create - outbound - form : deep ( . el - input ) , . create - outbound - form : deep ( . el - select ) , . create - outbound - form : deep ( . el - input - number ) { width : 100 % ; }
. form - hint { margin - top : 6 px ; color : var ( -- cc - muted ) ; font - size : 12 px ; }
. candidate - list { display : grid ; gap : 8 px ; max - height : 300 px ; overflow : auto ; padding : 8 px 0 ; }
. candidate - list : deep ( . el - checkbox ) { margin - right : 0 ; }
. candidate - row { display : grid ; grid - template - columns : minmax ( 120 px , 1 fr ) minmax ( 150 px , 1 fr ) auto ; gap : 12 px ; align - items : center ; width : min ( 700 px , 100 % ) ; }
. candidate - row span , . candidate - row em { color : var ( -- cc - muted ) ; font - style : normal ; font - size : 12 px ; }
. candidate - query { display : flex ; width : 100 % ; gap : 10 px ; }
. candidate - query : deep ( . el - input ) { flex : 1 ; }
. candidate - query : deep ( . el - button ) { flex : 0 0 auto ; min - width : 78 px ; margin - left : 0 ; }
. dropship - candidate - table { margin - top : 12 px ; width : 100 % ; }
. candidate - pagination { display : flex ; width : 100 % ; align - items : center ; justify - content : space - between ; gap : 12 px ; padding : 12 px 0 ; color : var ( -- cc - muted ) ; font - size : 12 px ; }
. return - lists { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) minmax ( 0 , 1 fr ) ; gap : 20 px ; width : 100 % ; margin : 4 px 0 16 px ; }
. return - list - card { min - width : 0 ; padding : 16 px ; border : 1 px solid var ( -- el - border - color - light ) ; border - radius : 10 px ; background : var ( -- el - fill - color - extra - light ) ; }
. list - title { display : flex ; align - items : baseline ; justify - content : space - between ; gap : 12 px ; margin - bottom : 12 px ; }
. list - title span { color : var ( -- el - text - color - primary ) ; font - size : 16 px ; font - weight : 650 ; }
. list - title small { color : var ( -- cc - muted ) ; font - size : 12 px ; font - weight : 400 ; }
. return - result - table { width : 100 % ; margin - top : 12 px ; background : var ( -- el - bg - color ) ; }
. return - pagination { display : flex ; min - height : 44 px ; align - items : center ; justify - content : space - between ; gap : 12 px ; padding - top : 12 px ; color : var ( -- cc - muted ) ; font - size : 12 px ; }
. return - pagination : deep ( . el - pagination ) { flex - wrap : wrap ; justify - content : flex - end ; }
@ media ( max - width : 980 px ) { . return - lists { grid - template - columns : 1 fr ; } . return - pagination { align - items : flex - start ; flex - direction : column ; } }
< / style >
< / style >