|
|
|
@@ -116,7 +116,7 @@ const columns: DataTableColumn[] = [
|
|
|
|
|
{ key: 'orderStatus', label: '订单 / 流程状态', minWidth: 156, slotName: 'orderStatus' },
|
|
|
|
|
{ key: 'measureReviewStatus', label: '测量数据审核状态', minWidth: 142, slotName: 'measureReviewStatus' },
|
|
|
|
|
{ key: 'expectedArrivalAt', label: '预计到达时间', minWidth: 164, slotName: 'expectedArrivalAt' },
|
|
|
|
|
{ key: 'feeStatus', label: '费用状态', minWidth: 124, slotName: 'feeStatus' },
|
|
|
|
|
{ key: 'feeStatus', label: '费用审核状态', minWidth: 132, slotName: 'feeStatus' },
|
|
|
|
|
{ key: 'customerName', label: '客户', minWidth: 142, slotName: 'customerName' },
|
|
|
|
|
{ key: 'warehouseName', label: '仓库', minWidth: 140, slotName: 'warehouseName' },
|
|
|
|
|
{ key: 'deliveryWarehouseName', label: '交货仓', minWidth: 140, slotName: 'deliveryWarehouseName' },
|
|
|
|
@@ -128,20 +128,20 @@ const columns: DataTableColumn[] = [
|
|
|
|
|
{ key: 'companyName', label: '公司', minWidth: 160, slotName: 'companyName' },
|
|
|
|
|
{ key: 'businessType', label: '业务类型', minWidth: 112, slotName: 'businessType' },
|
|
|
|
|
{ key: 'channelName', label: '渠道', minWidth: 138, slotName: 'channelName' },
|
|
|
|
|
{ key: 'taxMode', label: '计税模式', minWidth: 108, slotName: 'taxMode' },
|
|
|
|
|
{ key: 'containerMode', label: '装载模式', minWidth: 108, slotName: 'containerMode' },
|
|
|
|
|
{ key: 'taxMode', label: '买单/退税', minWidth: 108, slotName: 'taxMode' },
|
|
|
|
|
{ key: 'containerMode', label: '是否整柜', minWidth: 108, slotName: 'containerMode' },
|
|
|
|
|
{ key: 'customsInspection', label: '商检验', width: 92, slotName: 'customsInspection' },
|
|
|
|
|
{ key: 'sailingAt', label: '开船时间', minWidth: 164, slotName: 'sailingAt' },
|
|
|
|
|
{ key: 'portArrivalAt', label: '到港时间', minWidth: 164, slotName: 'portArrivalAt' },
|
|
|
|
|
{ key: 'totalPackageCount', label: '总箱数', width: 90, slotName: 'totalPackageCount' },
|
|
|
|
|
{ key: 'actualGrossWeightKg', label: '总毛重(kg)', minWidth: 118, slotName: 'actualGrossWeightKg' },
|
|
|
|
|
{ key: 'chargeableWeightKg', label: '计费重量(kg)', minWidth: 126, slotName: 'chargeableWeightKg' },
|
|
|
|
|
{ key: 'actualVolumeCbm', label: '总体积(CBM)', minWidth: 122, slotName: 'actualVolumeCbm' },
|
|
|
|
|
{ key: 'chargeableVolumeCbm', label: '计费体积(CBM)', minWidth: 132, slotName: 'chargeableVolumeCbm' },
|
|
|
|
|
{ key: 'actualGrossWeightKg', label: '总毛重(T.K.GW)', minWidth: 136, slotName: 'actualGrossWeightKg' },
|
|
|
|
|
{ key: 'chargeableWeightKg', label: '计费毛重', minWidth: 112, slotName: 'chargeableWeightKg' },
|
|
|
|
|
{ key: 'actualVolumeCbm', label: '总体积(m³)', minWidth: 118, slotName: 'actualVolumeCbm' },
|
|
|
|
|
{ key: 'chargeableVolumeCbm', label: '计费体积(m³)', minWidth: 128, slotName: 'chargeableVolumeCbm' },
|
|
|
|
|
{ key: 'palletCount', label: '总托盘数', width: 100, slotName: 'palletCount' },
|
|
|
|
|
{ key: 'locationCodes', label: '库位位置', minWidth: 124, slotName: 'locationCodes' },
|
|
|
|
|
{ key: 'volumeWeightKg', label: '总材积重(kg)', minWidth: 126, slotName: 'volumeWeightKg' },
|
|
|
|
|
{ key: 'actualNetWeightKg', label: '总净重(kg)', minWidth: 118, slotName: 'actualNetWeightKg' },
|
|
|
|
|
{ key: 'volumeWeightKg', label: '总材积重', minWidth: 110, slotName: 'volumeWeightKg' },
|
|
|
|
|
{ key: 'actualNetWeightKg', label: '总净重(T.K.NW)', minWidth: 136, slotName: 'actualNetWeightKg' },
|
|
|
|
|
{ key: 'destinationType', label: '目的仓类型', minWidth: 112, slotName: 'destinationType' },
|
|
|
|
|
{ key: 'destinationName', label: '目的仓', minWidth: 132, slotName: 'destinationName' },
|
|
|
|
|
{ key: 'measureCompletedAt', label: '测量完成时间', minWidth: 164, slotName: 'measureCompletedAt' },
|
|
|
|
@@ -156,6 +156,16 @@ const selectedCustomerName = computed(() => {
|
|
|
|
|
return customer?.legalName?.trim() || customer?.tenantName?.trim() || '全部客户'
|
|
|
|
|
})
|
|
|
|
|
const listEmptyText = '暂无符合条件的入库单'
|
|
|
|
|
const currentPageCount = computed(() => result.value.records.length)
|
|
|
|
|
const aggregateStats = computed(() => {
|
|
|
|
|
const records = result.value.records
|
|
|
|
|
return {
|
|
|
|
|
packages: records.reduce((total, item) => total + (item.totalPackageCount || 0), 0),
|
|
|
|
|
grossWeight: records.reduce((total, item) => total + (item.actualGrossWeightKg || 0), 0),
|
|
|
|
|
chargeableWeight: records.reduce((total, item) => total + (item.chargeableWeightKg || 0), 0),
|
|
|
|
|
volume: records.reduce((total, item) => total + (item.actualVolumeCbm || 0), 0),
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function queryValue(value: string) { return value.trim() || undefined }
|
|
|
|
|
function handleTabKeydown(event: KeyboardEvent, currentIndex: number) {
|
|
|
|
@@ -186,7 +196,7 @@ async function loadWarehouseOptions() {
|
|
|
|
|
}
|
|
|
|
|
warehousesLoading.value = true
|
|
|
|
|
try {
|
|
|
|
|
const response = await listPlatformWarehouses(selectedCustomerId.value, 1, 200)
|
|
|
|
|
const response = await listPlatformWarehouses(1, 200)
|
|
|
|
|
if (disposed || requestId !== warehouseRequestId) return
|
|
|
|
|
warehouseOptions.value = response.records.map((warehouse) => ({
|
|
|
|
|
label: `${warehouse.warehouseName} · ${warehouse.warehouseCode}`,
|
|
|
|
@@ -297,6 +307,10 @@ function openWorkspace(row: InboundOrderSummary) {
|
|
|
|
|
void router.push({ name: 'inbound-order-detail', params: { inboundId: row.id }, query: { tenantId: row.tenantId } })
|
|
|
|
|
}
|
|
|
|
|
function formatNumber(value: number | null, digits = 3) { return value === null ? '—' : value.toFixed(digits) }
|
|
|
|
|
function formatAggregate(value: number, digits = 3) {
|
|
|
|
|
if (!Number.isFinite(value)) return '0'
|
|
|
|
|
return value.toLocaleString('zh-CN', { maximumFractionDigits: digits })
|
|
|
|
|
}
|
|
|
|
|
function formatDate(value: string | null) {
|
|
|
|
|
if (!value) return '—'
|
|
|
|
|
const date = new Date(value)
|
|
|
|
@@ -390,8 +404,20 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; detailRequestId +=
|
|
|
|
|
<button v-for="(item, index) in tabs" :key="item.value || 'all'" type="button" role="tab" class="inbound-tab" :class="{ 'is-active': tab === item.value }" :aria-selected="tab === item.value" :tabindex="tab === item.value ? 0 : -1" :title="item.description" @click="tab = item.value" @keydown="handleTabKeydown($event, index)">{{ item.label }}</button>
|
|
|
|
|
</nav>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="inbound-stats-row" aria-label="入库单统计">
|
|
|
|
|
<span class="selected-info">当前显示 {{ currentPageCount }} 条 · 共 {{ result.total }} 条</span>
|
|
|
|
|
<span class="total-info">
|
|
|
|
|
<b>总箱数 {{ formatAggregate(aggregateStats.packages, 0) }}</b>
|
|
|
|
|
<i aria-hidden="true" />
|
|
|
|
|
<b>总毛重 {{ formatAggregate(aggregateStats.grossWeight) }} kg</b>
|
|
|
|
|
<i aria-hidden="true" />
|
|
|
|
|
<b>计费重量 {{ formatAggregate(aggregateStats.chargeableWeight) }} kg</b>
|
|
|
|
|
<i aria-hidden="true" />
|
|
|
|
|
<b>总体积 {{ formatAggregate(aggregateStats.volume) }} m³</b>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="table-action-row">
|
|
|
|
|
<span class="result-summary">共 {{ result.total }} 条 · 所属企业 {{ selectedCustomerName }}</span>
|
|
|
|
|
<span class="result-summary">所属企业 {{ selectedCustomerName }}</span>
|
|
|
|
|
<div class="table-action-buttons">
|
|
|
|
|
<el-button :loading="loading" @click="loadInbounds"><RefreshCw :size="16" />刷新</el-button>
|
|
|
|
|
</div>
|
|
|
|
@@ -442,37 +468,50 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; detailRequestId +=
|
|
|
|
|
|
|
|
|
|
<AppDrawer v-model="detailOpen" :title="detail ? `入库单详情 · ${detail.summary.inboundCode || detail.summary.id}` : '入库单详情'" size="min(720px, 94vw)" :loading="detailLoading" :show-footer="false" destroy-on-close @closed="closeDetail">
|
|
|
|
|
<section v-if="detailError" class="detail-error" role="alert"><strong>入库单详情暂时无法加载</strong><p>{{ detailError.message }}</p><ApiErrorMeta :details="detailError" /><el-button type="primary" :disabled="!detailTarget" @click="detailTarget && openDetail(detailTarget)">重新加载</el-button></section>
|
|
|
|
|
<div v-else-if="detail" class="detail-content"><InboundSummaryPanel :summary="detail.summary" /><InboundHistoryPanel :pallet-locations="detail.palletLocations" :files="detail.files" :internal-notes="detail.internalNotes" :status-history="detail.statusHistory" :public-remark="detail.publicRemark" /></div>
|
|
|
|
|
<div v-else-if="detail" class="detail-content"><InboundSummaryPanel :summary="detail.summary" :public-remark="detail.publicRemark" /><InboundHistoryPanel :pallet-locations="detail.palletLocations" :files="detail.files" :internal-notes="detail.internalNotes" :status-history="detail.statusHistory" :public-remark="null" /></div>
|
|
|
|
|
</AppDrawer>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.inbound-orders-page { min-width: 0; }
|
|
|
|
|
.inbound-tabs { display: flex; width: max-content; max-width: 100%; min-width: 0; overflow-x: auto; padding: 4px; border: 1px solid var(--cc-border); border-radius: 8px; background: #f7f9fc; scrollbar-width: none; }
|
|
|
|
|
.inbound-query-toolbar { align-items: stretch; border-radius: 8px; background: #f8fafc; }
|
|
|
|
|
.inbound-query-toolbar :deep(.query-toolbar__filters) { display: grid; flex: 1 1 auto; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px 16px; }
|
|
|
|
|
.inbound-query-toolbar :deep(.query-toolbar__filters .el-input),
|
|
|
|
|
.inbound-query-toolbar :deep(.query-toolbar__filters .el-select),
|
|
|
|
|
.inbound-query-toolbar :deep(.query-toolbar__filters .el-date-editor) { width: 100%; }
|
|
|
|
|
.inbound-query-toolbar :deep(.query-toolbar__actions) { align-content: flex-end; align-items: flex-end; }
|
|
|
|
|
.inbound-tabs { display: flex; width: max-content; max-width: 100%; min-width: 0; overflow-x: auto; border-bottom: 2px solid var(--cc-border); scrollbar-width: none; }
|
|
|
|
|
.inbound-tabs::-webkit-scrollbar { display: none; }
|
|
|
|
|
.inbound-tab { min-height: 44px; flex: 0 0 auto; padding: 8px 18px; border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--cc-muted); cursor: pointer; font-size: 13px; line-height: 1.4; white-space: nowrap; transition: background-color .16s ease, border-color .16s ease, color .16s ease, box-shadow .16s ease; }
|
|
|
|
|
.inbound-tab:hover { background: #fff; color: var(--cc-primary); }
|
|
|
|
|
.inbound-tab { min-height: 42px; flex: 0 0 auto; padding: 10px 22px; border: 0; border-bottom: 2px solid transparent; margin-bottom: -2px; background: transparent; color: #475467; cursor: pointer; font-size: 13px; line-height: 1.4; white-space: nowrap; transition: border-color .16s ease, color .16s ease, background-color .16s ease; }
|
|
|
|
|
.inbound-tab:hover { color: var(--cc-primary); background: #f8fbff; }
|
|
|
|
|
.inbound-tab:focus-visible { outline: 3px solid rgba(102, 126, 234, .2); outline-offset: 1px; }
|
|
|
|
|
.inbound-tab.is-active { border-color: #d8e0f0; background: #fff; color: var(--cc-primary); box-shadow: 0 1px 3px rgba(16, 24, 40, .08); font-weight: 650; }
|
|
|
|
|
.inbound-tab.is-active { border-bottom-color: var(--cc-primary); background: #fff; color: var(--cc-primary); font-weight: 650; }
|
|
|
|
|
.inbound-load-error { margin-top: 12px; }
|
|
|
|
|
.inbound-query-toolbar { border-radius: 8px; }
|
|
|
|
|
.row-actions { display: flex; align-items: center; gap: 2px; white-space: nowrap; }
|
|
|
|
|
.row-actions :deep(.el-button) { margin-left: 0; }
|
|
|
|
|
.table-toolbar { display: grid; min-width: 0; border-bottom: 1px solid var(--cc-border); }
|
|
|
|
|
.table-tabs-row { min-width: 0; padding: 10px 14px; }
|
|
|
|
|
.table-action-row { display: flex; min-width: 0; min-height: 56px; align-items: center; justify-content: space-between; gap: 16px; padding: 8px 14px; border-top: 1px solid var(--cc-border); background: #fbfcfe; }
|
|
|
|
|
.table-tabs-row { min-width: 0; padding: 0 14px; background: #fff; }
|
|
|
|
|
.inbound-stats-row { display: flex; min-width: 0; align-items: center; justify-content: space-between; gap: 16px; padding: 10px 14px; background: var(--cc-primary); color: #fff; }
|
|
|
|
|
.inbound-stats-row .selected-info, .inbound-stats-row .total-info { min-width: 0; font-size: 12px; font-weight: 650; line-height: 1.5; }
|
|
|
|
|
.inbound-stats-row .total-info { display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
|
|
|
|
|
.inbound-stats-row b { font-weight: 650; }
|
|
|
|
|
.inbound-stats-row i { width: 1px; height: 12px; background: rgba(255, 255, 255, .38); }
|
|
|
|
|
.table-action-row { display: flex; min-width: 0; min-height: 48px; align-items: center; justify-content: space-between; gap: 16px; padding: 7px 14px; border-top: 1px solid #dfe6f0; background: #fbfcfe; }
|
|
|
|
|
.table-action-buttons { display: flex; min-width: 0; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
|
|
|
|
|
.result-summary { color: var(--cc-muted); font-size: 12px; white-space: nowrap; }
|
|
|
|
|
.table-action-buttons :deep(.el-button) { min-height: 40px; margin-left: 0; }
|
|
|
|
|
.table-action-buttons :deep(.el-button) { min-height: 34px; margin-left: 0; }
|
|
|
|
|
.table-panel :deep(.el-table th.el-table__cell) { border-right: 1px solid #edf0f4; }
|
|
|
|
|
.table-panel :deep(.el-table td.el-table__cell) { border-right: 1px solid #edf0f4; }
|
|
|
|
|
.primary-cell, .status-stack { display: grid; min-width: 0; justify-items: start; gap: 4px; }
|
|
|
|
|
.primary-cell strong, .primary-cell span, .status-stack span, .status-stack small { overflow-wrap: anywhere; }
|
|
|
|
|
.primary-cell span, .status-stack span, .status-stack small { color: var(--cc-muted); font-size: 12px; }
|
|
|
|
|
.detail-content { display: grid; gap: 24px; min-width: 0; }
|
|
|
|
|
.detail-error { display: flex; min-height: 240px; align-items: flex-start; justify-content: center; flex-direction: column; }
|
|
|
|
|
.detail-error strong { color: var(--cc-danger); font-size: 15px; }
|
|
|
|
|
.detail-error p { margin: 7px 0; color: var(--cc-text); font-size: 13px; }
|
|
|
|
|
.detail-error strong { color: #b42318; font-size: 16px; }
|
|
|
|
|
.detail-error p { margin: 7px 0; color: #172033; font-size: 13px; }
|
|
|
|
|
.detail-error :deep(.el-button) { margin-top: 14px; }
|
|
|
|
|
@media (max-width: 900px) { .inbound-tabs { width: 100%; } }
|
|
|
|
|
@media (max-width: 720px) { .inbound-tab { padding-inline: 14px; } .table-tabs-row { padding: 10px; } .table-action-row { min-height: 0; align-items: stretch; flex-direction: column; gap: 10px; padding: 10px; } .table-action-buttons { justify-content: flex-start; } .result-summary { min-width: 0; overflow: hidden; text-overflow: ellipsis; } .table-action-buttons :deep(.el-button) { min-height: 44px; } }
|
|
|
|
|
@media (max-width: 1180px) { .inbound-query-toolbar :deep(.query-toolbar__filters) { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
|
|
|
|
|
@media (max-width: 900px) { .inbound-tabs { width: 100%; } .inbound-query-toolbar :deep(.query-toolbar__filters) { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
|
|
|
@media (max-width: 720px) { .inbound-query-toolbar :deep(.query-toolbar__filters) { grid-template-columns: 1fr; } .inbound-tab { padding-inline: 14px; } .table-tabs-row { padding: 0 10px; } .inbound-stats-row, .table-action-row { min-height: 0; align-items: stretch; flex-direction: column; gap: 10px; padding: 10px; } .inbound-stats-row .total-info { justify-content: flex-start; } .table-action-buttons { justify-content: flex-start; } .result-summary { min-width: 0; overflow: hidden; text-overflow: ellipsis; } .table-action-buttons :deep(.el-button) { min-height: 44px; } }
|
|
|
|
|
</style>
|
|
|
|
|