This commit is contained in:
2026-08-10 16:24:54 +08:00
parent e119b48662
commit 820432a381
52 changed files with 1015 additions and 705 deletions
@@ -4,9 +4,13 @@ import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { ChevronDown, ChevronUp, ClipboardCheck, Eye, Printer, RefreshCw, SlidersHorizontal } from '@lucide/vue'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import AdminPage from '@/shared/components/AdminPage.vue'
import AppDrawer from '@/shared/components/AppDrawer.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
import FormSection from '@/shared/components/FormSection.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
@@ -400,25 +404,32 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; detailRequestId +=
</script>
<template>
<div class="business-page inbound-orders-page">
<QueryToolbar v-model="filters" class="surface-panel inbound-query-toolbar" ariaLabel="入库单筛选" :fields="queryFields" :available-fields="allQueryFields" preference-key="inbound-orders-query" :search-on-reset="false" @search="applyFilters" @reset="resetFilters">
<template #actions>
<el-button :aria-expanded="showAdvancedFilters" @click="toggleAdvancedFilters">
<SlidersHorizontal :size="16" />
{{ showAdvancedFilters ? '收起筛选' : '更多筛选' }}
<ChevronUp v-if="showAdvancedFilters" :size="15" />
<ChevronDown v-else :size="15" />
</el-button>
<AdminPage as="div" class="business-page inbound-orders-page">
<ListPageLayout ariaLabel="入库单管理">
<template #toolbar>
<QueryToolbar v-model="filters" class="surface-panel inbound-query-toolbar" ariaLabel="入库单筛选" :fields="queryFields" :available-fields="allQueryFields" preference-key="inbound-orders-query" :search-on-reset="false" @search="applyFilters" @reset="resetFilters">
<template #actions>
<el-button :aria-expanded="showAdvancedFilters" @click="toggleAdvancedFilters">
<SlidersHorizontal :size="16" />
{{ showAdvancedFilters ? '收起筛选' : '更多筛选' }}
<ChevronUp v-if="showAdvancedFilters" :size="15" />
<ChevronDown v-else :size="15" />
</el-button>
</template>
</QueryToolbar>
</template>
</QueryToolbar>
<el-alert v-if="customerLoadError" type="warning" :closable="false" show-icon role="alert">
<template #title><div class="api-error-alert-title"><span>{{ customerLoadError.message }}</span><ApiErrorMeta :details="customerLoadError" /></div></template>
<el-button link type="warning" :loading="customersLoading" @click="loadCustomers">重新加载客户列表</el-button>
</el-alert>
<template v-if="customerLoadError || loadError" #state>
<div class="inbound-state-stack">
<el-alert v-if="customerLoadError" type="warning" :closable="false" show-icon role="alert">
<template #title><div class="api-error-alert-title"><span>{{ customerLoadError.message }}</span><ApiErrorMeta :details="customerLoadError" /></div></template>
<el-button link type="warning" :loading="customersLoading" @click="loadCustomers"><RefreshCw :size="15" />重新加载客户列表</el-button>
</el-alert>
<LoadErrorState v-if="loadError" :message="loadError.message" title="入库单暂时无法加载" @retry="loadInbounds"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState>
</div>
</template>
<LoadErrorState v-if="loadError" class="inbound-load-error" :message="loadError.message" title="入库单暂时无法加载" @retry="loadInbounds"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState>
<section v-else class="surface-panel table-panel" :aria-busy="loading">
<section v-if="!loadError" class="surface-panel table-panel" :aria-busy="loading">
<header class="table-toolbar">
<div class="table-tabs-row">
<nav class="inbound-tabs" role="tablist" aria-label="入库单处理状态">
@@ -426,7 +437,6 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; detailRequestId +=
</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" />
@@ -439,8 +449,7 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; detailRequestId +=
</div>
</header>
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="result.records" :columns="columns" preference-key="inbound-orders-table" :loading="loading" :total="result.total" :page-sizes="[20, 50, 100]" row-key="id" :empty-text="listEmptyText" aria-label="入库单列表">
<template #toolbar-start><div><h2>入库单列表</h2><p>所属企业 {{ selectedCustomerName }}</p></div></template>
<template #toolbar-actions><el-button :loading="loading" @click="loadInbounds"><RefreshCw :size="16" />刷新</el-button></template>
<template #pagination-left>当前显示 {{ currentPageCount }} 条 · 共 {{ result.total }} 条</template>
<template #actions="{ row }"><div class="row-actions"><el-button link type="primary" @click="openWorkspace(row as InboundOrderSummary)"><ClipboardCheck :size="15" />处理</el-button><el-button link :loading="printingInboundId === (row as InboundOrderSummary).id" :disabled="Boolean(printingInboundId)" @click="printCartonLabels(row as InboundOrderSummary)"><Printer :size="15" />打印箱唛</el-button><el-button link @click="openDetail(row as InboundOrderSummary)"><Eye :size="15" />查看</el-button></div></template>
<template #inbound="{ row }"><div class="primary-cell"><strong>{{ text((row as InboundOrderSummary).inboundCode) }}</strong><span>订单 {{ text((row as InboundOrderSummary).orderNo) }}</span></div></template>
<template #orderStatus="{ row }"><div class="status-stack"><el-tag :type="statusType((row as InboundOrderSummary).processStatus)" effect="light">{{ statusLabel((row as InboundOrderSummary).processStatus) }}</el-tag><span>订单:{{ text((row as InboundOrderSummary).orderStatusSnapshot) }}</span></div></template>
@@ -481,13 +490,19 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; detailRequestId +=
<template #createdAt="{ row }">{{ formatDate((row as InboundOrderSummary).createdAt) }}</template>
<template #empty><el-empty :description="listEmptyText" /></template>
</DataTable>
</section>
</section>
</ListPageLayout>
<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" :public-remark="detail.publicRemark" /><InboundHistoryPanel :pallet-locations="detail.palletLocations" :files="detail.files" :internal-notes="detail.internalNotes" :status-history="detail.statusHistory" :public-remark="null" /></div>
<DetailPageLayout aria-label="入库单详情抽屉">
<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)"><RefreshCw :size="16" />重新加载</el-button></section>
<div v-else-if="detail" class="detail-content">
<FormSection title="入库概览"><InboundSummaryPanel :summary="detail.summary" :public-remark="detail.publicRemark" /></FormSection>
<FormSection title="资源与状态记录" :bordered="false"><InboundHistoryPanel :pallet-locations="detail.palletLocations" :files="detail.files" :internal-notes="detail.internalNotes" :status-history="detail.statusHistory" :public-remark="null" /></FormSection>
</div>
</DetailPageLayout>
</AppDrawer>
</div>
</AdminPage>
</template>
<style scoped>
@@ -499,7 +514,7 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; detailRequestId +=
.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-bottom-color: var(--cc-primary); background: #fff; color: var(--cc-primary); font-weight: 650; }
.inbound-load-error { margin-top: 12px; }
.inbound-state-stack { display: grid; min-width: 0; gap: 12px; }
.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); }
@@ -521,4 +536,5 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; detailRequestId +=
.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: 0 10px; } .inbound-stats-row { min-height: 0; align-items: stretch; flex-direction: column; gap: 10px; padding: 10px; } .inbound-stats-row .total-info { justify-content: flex-start; } }
@media (max-width: 360px) { .inbound-tab { padding-inline: 12px; } .row-actions { align-items: flex-start; flex-direction: column; } }
</style>