优化
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ArrowLeft, Box, ChevronDown, ChevronUp, CircleCheck, Eye, History, ListChecks, MoreHorizontal, PackageOpen, RefreshCw, Ruler, SlidersHorizontal, StickyNote, WalletCards } from '@lucide/vue'
|
||||
import { ArrowLeft, Box, ChevronDown, ChevronUp, CircleCheck, Eye, History, ListChecks, MoreHorizontal, PackageOpen, Ruler, SlidersHorizontal, StickyNote, WalletCards } from '@lucide/vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import {
|
||||
getPlatformUnifiedOrder,
|
||||
@@ -998,22 +998,21 @@ onMounted(() => {
|
||||
>{{ tab.label }}</button>
|
||||
</div>
|
||||
|
||||
<QueryToolbar id="unified-orders-advanced-filters" v-model="filters" class="orders-query-toolbar" ariaLabel="订单列表筛选" :fields="queryFields" :available-fields="allQueryFields" preference-key="unified-orders-query-v2" :search-on-reset="false" @search="search" @reset="reset">
|
||||
<template #actions>
|
||||
<el-button :aria-expanded="showAdvancedFilters" aria-controls="unified-orders-advanced-filters" @click="showAdvancedFilters = !showAdvancedFilters">
|
||||
<SlidersHorizontal :size="16" />{{ showAdvancedFilters ? '收起筛选' : '高级筛选' }}
|
||||
<ChevronUp v-if="showAdvancedFilters" :size="15" /><ChevronDown v-else :size="15" />
|
||||
</el-button>
|
||||
</template>
|
||||
</QueryToolbar>
|
||||
<div class="filter-mode-actions">
|
||||
<el-button :aria-expanded="showAdvancedFilters" aria-controls="unified-orders-advanced-filters" @click="showAdvancedFilters = !showAdvancedFilters">
|
||||
<SlidersHorizontal :size="16" />{{ showAdvancedFilters ? '收起筛选' : '高级筛选' }}
|
||||
<ChevronUp v-if="showAdvancedFilters" :size="15" /><ChevronDown v-else :size="15" />
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<QueryToolbar id="unified-orders-advanced-filters" v-model="filters" class="orders-query-toolbar" ariaLabel="订单列表筛选" :fields="queryFields" :available-fields="allQueryFields" preference-key="unified-orders-query-v2" :search-on-reset="false" @search="search" @reset="reset" />
|
||||
</div>
|
||||
<LoadErrorState v-if="loadError" class="orders-load-error" title="订单列表暂时无法加载" :message="loadError.message" :loading="loading" @retry="loadOrders">
|
||||
<template #details><ApiErrorMeta :details="loadError" /></template>
|
||||
</LoadErrorState>
|
||||
|
||||
<div v-else id="unified-orders-results" class="table-card" role="tabpanel">
|
||||
<div v-else id="unified-orders-results" class="surface-panel table-panel table-card" role="tabpanel">
|
||||
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="records" :columns="columns" preference-key="unified-orders-table" :loading="loading" :total="total" :page-sizes="[20, 50, 100]" row-key="orderId" empty-text="暂无符合条件的订单" aria-label="统一订单列表" @selection-change="handleSelectionChange">
|
||||
<template #toolbar-start><div><h2>统一订单列表</h2><p>共 {{ total }} 条订单</p></div></template>
|
||||
<template #toolbar-actions>
|
||||
<el-button type="primary" plain :disabled="selectedOrders.length === 0" title="批量审核接口待接入" @click="openBulkReviewPlaceholder"><ListChecks :size="16" />批量审核<span v-if="selectedOrders.length">({{ selectedOrders.length }})</span></el-button>
|
||||
</template>
|
||||
@@ -1021,7 +1020,7 @@ onMounted(() => {
|
||||
<div class="row-actions">
|
||||
<el-button link type="primary" @click="openDetail(row as UnifiedOrderResource)"><Eye :size="15" />详情</el-button>
|
||||
<el-dropdown trigger="click" @command="runMoreAction($event, row as UnifiedOrderResource)">
|
||||
<el-button link type="primary" aria-label="更多订单操作"><MoreHorizontal :size="17" />更多</el-button>
|
||||
<el-button link type="primary" aria-label="更多订单操作" title="更多操作"><MoreHorizontal :size="17" /></el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-if="can('platform:order:note')" command="note"><StickyNote :size="15" />添加备注</el-dropdown-item>
|
||||
@@ -1045,6 +1044,7 @@ onMounted(() => {
|
||||
<template #cargo="{ row }"><span>{{ (row as UnifiedOrderResource).totalPackageCount ?? '—' }} 箱</span><span v-if="(row as UnifiedOrderResource).measurement?.actualGrossWeightKg !== null && (row as UnifiedOrderResource).measurement?.actualGrossWeightKg !== undefined"> · {{ formatNumber((row as UnifiedOrderResource).measurement?.actualGrossWeightKg) }} kg</span><span v-if="(row as UnifiedOrderResource).measurement?.actualVolumeCbm !== null && (row as UnifiedOrderResource).measurement?.actualVolumeCbm !== undefined"> · {{ formatNumber((row as UnifiedOrderResource).measurement?.actualVolumeCbm, 3) }} m³</span></template>
|
||||
<template #expectedShipDate="{ row }">{{ formatDate((row as UnifiedOrderResource).expectedShipDate) }}</template>
|
||||
<template #updatedAt="{ row }">{{ formatDate((row as UnifiedOrderResource).updatedAt) }}</template>
|
||||
<template #pagination-left="{ total: tableTotal }"><span>共 {{ tableTotal }} 条</span></template>
|
||||
</DataTable>
|
||||
</div>
|
||||
</ListPageLayout>
|
||||
@@ -1070,6 +1070,7 @@ onMounted(() => {
|
||||
.page-intro :deep(.el-button) { flex: 0 0 auto; }
|
||||
.orders-query-panel { overflow: hidden; border: 1px solid var(--cc-border); border-radius: 8px; background: #fff; }
|
||||
.status-tabs { display: flex; max-width: 100%; overflow-x: auto; margin-bottom: 0; border-bottom: 1px solid var(--cc-border); scrollbar-width: thin; }
|
||||
.filter-mode-actions { display: flex; justify-content: flex-end; padding: 12px 16px 0; }
|
||||
.status-tab { position: relative; flex: 0 0 auto; min-height: 44px; padding: 0 18px; border: 0; background: transparent; color: var(--cc-muted); font: inherit; font-size: 14px; cursor: pointer; }
|
||||
.status-tab::after { position: absolute; right: 12px; bottom: 0; left: 12px; height: 3px; background: transparent; content: ''; }
|
||||
.status-tab:hover { color: var(--cc-primary); background: #f6f9fd; }
|
||||
@@ -1077,8 +1078,8 @@ onMounted(() => {
|
||||
.status-tab.is-active { color: var(--cc-primary); font-weight: 600; }
|
||||
.status-tab.is-active::after { background: var(--cc-primary); }
|
||||
.orders-query-toolbar { border: 0; }
|
||||
.orders-load-error { margin-top: 12px; }
|
||||
.table-card { overflow: hidden; border: 1px solid var(--cc-border); border-radius: 8px; background: #fff; }
|
||||
.orders-load-error { margin-top: 0; }
|
||||
.table-card { overflow: hidden; }
|
||||
.row-actions { display: flex; align-items: center; gap: 4px; white-space: nowrap; }
|
||||
.row-actions :deep(.el-button) { margin-left: 0; }
|
||||
.primary-cell { display: grid; min-width: 0; gap: 4px; }
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { Check, Plus, RefreshCw, Sparkles, Trash2 } from '@lucide/vue'
|
||||
import { Check, Pencil, Plus, RefreshCw, Sparkles, Trash2 } from '@lucide/vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.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 { ApiResponseError, getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
@@ -267,12 +268,9 @@ onMounted(() => { void load() })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="smart-loading" aria-labelledby="smart-loading-title">
|
||||
<section class="smart-loading" aria-label="智能排柜工作台">
|
||||
<header class="smart-loading__heading">
|
||||
<div>
|
||||
<h3 id="smart-loading-title">智能排柜</h3>
|
||||
<p>{{ container?.cabinetNo || container?.containerNo || `柜次 ${bookingId}` }} · {{ container?.warehouseName || '仓库待加载' }}</p>
|
||||
</div>
|
||||
<div class="smart-loading__context"><span>当前柜次</span><strong>{{ container?.cabinetNo || container?.containerNo || `柜次 ${bookingId}` }}</strong><small>{{ container?.warehouseName || '仓库待加载' }}</small></div>
|
||||
<div class="smart-loading__actions">
|
||||
<el-button circle :loading="loading" aria-label="刷新智能排柜" @click="load"><RefreshCw :size="16" /></el-button>
|
||||
<el-button v-if="!plan" type="primary" :disabled="!canCreate || loading" :loading="action === 'create'" @click="createPlan"><Plus :size="15" />创建装柜草稿</el-button>
|
||||
@@ -299,11 +297,11 @@ onMounted(() => { void load() })
|
||||
<div><span>重量</span><strong>{{ formatNumber(usedWeight) }} / {{ formatNumber(container.maxWeightKg || 0) }} kg</strong><small>{{ weightRate == null ? '承重待配置' : `${weightRate}%` }}</small></div>
|
||||
</div>
|
||||
|
||||
<section v-if="plan" class="smart-loading__section">
|
||||
<section v-if="plan" class="smart-loading__section surface-panel table-panel">
|
||||
<div class="smart-loading__section-title"><h4>当前装柜方案</h4><span>版本 {{ plan.version }} · {{ plan.planNo || '—' }}</span></div>
|
||||
<DataTable :data="plan.items" :columns="itemColumns" :loading="loading" empty-text="装柜草稿暂无明细">
|
||||
<template #actions="{ row }">
|
||||
<el-button link type="primary" :disabled="!canAssign || !editable || Boolean(action)" @click="openAdjust(row as SmartLoadingPlanItem)">调整</el-button>
|
||||
<el-button link type="primary" :disabled="!canAssign || !editable || Boolean(action)" @click="openAdjust(row as SmartLoadingPlanItem)"><Pencil :size="14" />调整</el-button>
|
||||
<el-button link type="danger" :disabled="!canDelete || !editable || Boolean(action)" @click="openRemove(row as SmartLoadingPlanItem)"><Trash2 :size="13" />移除</el-button>
|
||||
</template>
|
||||
<template #order="{ row }"><strong>{{ (row as SmartLoadingPlanItem).orderNo || '—' }}</strong><small>{{ (row as SmartLoadingPlanItem).inboundCode || '—' }}</small></template>
|
||||
@@ -315,9 +313,11 @@ onMounted(() => { void load() })
|
||||
|
||||
<section class="smart-loading__section">
|
||||
<div class="smart-loading__section-title"><h4>候选货物</h4><span>后端按入库完成时间排序</span></div>
|
||||
<QueryToolbar v-model="filters" ariaLabel="候选货物筛选" :fields="queryFields" @search="applyFilters" @reset="applyFilters" />
|
||||
<ListPageLayout ariaLabel="智能排柜候选货物">
|
||||
<template #toolbar><QueryToolbar v-model="filters" ariaLabel="候选货物筛选" :fields="queryFields" @search="applyFilters" @reset="applyFilters" /></template>
|
||||
<section class="surface-panel table-panel">
|
||||
<DataTable :data="filteredCandidates" :columns="candidateColumns" :loading="loading" empty-text="当前柜次仓库暂无可用候选货物">
|
||||
<template #actions="{ row }"><el-button link type="primary" :disabled="!plan || !canAssign || !editable || fittingCount(row as SmartLoadingCandidate) <= 0 || Boolean(action)" @click="openAssign(row as SmartLoadingCandidate)">加入</el-button></template>
|
||||
<template #actions="{ row }"><el-button link type="primary" :disabled="!plan || !canAssign || !editable || fittingCount(row as SmartLoadingCandidate) <= 0 || Boolean(action)" @click="openAssign(row as SmartLoadingCandidate)"><Plus :size="14" />加入</el-button></template>
|
||||
<template #priority="{ $index }"><span class="priority">{{ Number($index) + 1 }}</span></template>
|
||||
<template #order="{ row }"><strong>{{ (row as SmartLoadingCandidate).orderNo || '—' }}</strong><small>{{ (row as SmartLoadingCandidate).inboundCode || '—' }}</small></template>
|
||||
<template #destination="{ row }">{{ (row as SmartLoadingCandidate).destinationWarehouseName || (row as SmartLoadingCandidate).destinationWarehouseCode || '—' }}</template>
|
||||
@@ -325,6 +325,8 @@ onMounted(() => { void load() })
|
||||
<template #unitWeight="{ row }">{{ formatNumber((row as SmartLoadingCandidate).unitWeightKg) }}</template>
|
||||
<template #explanation="{ row }"><el-tag size="small" :type="fittingCount(row as SmartLoadingCandidate) > 0 ? 'success' : 'danger'">{{ fittingCount(row as SmartLoadingCandidate) > 0 ? '可加入' : '冲突' }}</el-tag><span>{{ candidateExplanation(row as SmartLoadingCandidate) }}</span></template>
|
||||
</DataTable>
|
||||
</section>
|
||||
</ListPageLayout>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -356,13 +358,16 @@ onMounted(() => { void load() })
|
||||
.smart-loading { display: grid; gap: 14px; }
|
||||
.smart-loading__heading, .smart-loading__actions, .smart-loading__section-title { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
|
||||
.smart-loading__heading, .smart-loading__section-title { justify-content: space-between; }
|
||||
.smart-loading__heading h3, .smart-loading__section-title h4 { margin: 0; font-size: 15px; }
|
||||
.smart-loading__heading p, .smart-loading__section-title span, .smart-loading small { display: block; margin: 3px 0 0; color: var(--cc-text-secondary, #64748b); font-size: 12px; }
|
||||
.smart-loading__section-title h4 { margin: 0; font-size: 15px; }
|
||||
.smart-loading__context { display: grid; min-width: 0; gap: 3px; }
|
||||
.smart-loading__context span { color: var(--cc-text-secondary, #64748b); font-size: 11px; }
|
||||
.smart-loading__context strong { overflow: hidden; font-size: 14px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.smart-loading__section-title span, .smart-loading small { display: block; margin: 3px 0 0; color: var(--cc-text-secondary, #64748b); font-size: 12px; }
|
||||
.smart-loading__summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
|
||||
.smart-loading__summary > div { min-width: 0; padding: 10px; border: 1px solid var(--cc-border); border-radius: 5px; background: var(--el-fill-color-extra-light); }
|
||||
.smart-loading__summary span { display: block; margin-bottom: 4px; color: var(--cc-text-secondary, #64748b); font-size: 12px; }
|
||||
.smart-loading__summary strong { display: block; overflow-wrap: anywhere; font-size: 13px; }
|
||||
.smart-loading__section { display: grid; gap: 10px; padding-top: 4px; }
|
||||
.smart-loading__section { display: grid; min-width: 0; gap: 10px; padding-top: 4px; overflow: hidden; }
|
||||
.smart-loading__error p { margin: 6px 0 0; }
|
||||
.smart-loading :deep(.el-table__cell small), .smart-loading :deep(.el-table__cell strong) { display: block; }
|
||||
.smart-loading :deep(.el-table__cell small) { margin-top: 2px; color: var(--cc-text-secondary, #64748b); }
|
||||
|
||||
@@ -392,12 +392,9 @@ onMounted(() => { void refresh() })
|
||||
|
||||
<template>
|
||||
<section class="workflow-panel" aria-label="订单后半程作业">
|
||||
<div class="workflow-heading">
|
||||
<div>
|
||||
<h2>后半程作业</h2>
|
||||
<p>查看订单的分柜、预装、拣货和装柜进度。</p>
|
||||
</div>
|
||||
<el-button text :loading="loading" aria-label="刷新作业数据" @click="refresh"><RefreshCw :size="15" />刷新</el-button>
|
||||
<div class="workflow-heading" aria-label="订单作业上下文">
|
||||
<div class="workflow-context"><span>入库作业</span><strong>#{{ props.inboundId }}</strong></div>
|
||||
<el-tooltip content="刷新作业数据" placement="top"><el-button text :loading="loading" aria-label="刷新作业数据" @click="refresh"><RefreshCw :size="15" /></el-button></el-tooltip>
|
||||
</div>
|
||||
|
||||
<el-alert v-if="error" class="workflow-alert" type="error" :closable="false" show-icon>
|
||||
@@ -421,7 +418,7 @@ onMounted(() => { void refresh() })
|
||||
</el-card>
|
||||
|
||||
<template v-if="isSplit">
|
||||
<el-card shadow="never" class="workflow-card">
|
||||
<el-card shadow="never" class="workflow-card surface-panel table-panel">
|
||||
<template #header><div class="card-title"><span>入库预装清单</span><el-tag>{{ preloadStatusLabel(preload?.status) }}</el-tag></div></template>
|
||||
<el-alert v-if="!canPreloadView" type="warning" :closable="false" show-icon>当前账号无预装清单查询权限。</el-alert>
|
||||
<LoadErrorState v-else-if="preloadLoadError" title="预装清单暂时无法加载" :message="preloadLoadError.message" :loading="loading" @retry="loadPreload">
|
||||
@@ -434,7 +431,6 @@ onMounted(() => { void refresh() })
|
||||
<el-button link type="danger" :disabled="!canSplit || Boolean(busy)" :aria-label="`删除第 ${Number($index) + 1} 条预装明细`" @click="removeItem(Number($index))"><Trash2 :size="14" />删除</el-button>
|
||||
</template>
|
||||
<template #volume="{ row }">{{ formatVolume((row as PreloadItem).volumeCbm) }}</template>
|
||||
<template #toolbar-start><div><strong>明细</strong><p>入库级清单由人工维护;柜次候选和智能建议在下方柜次工作台加载。</p></div></template>
|
||||
<template #toolbar-actions><el-button :disabled="!canSplit || Boolean(busy)" @click="addItem"><Plus :size="15" />新增明细</el-button></template>
|
||||
</DataTable>
|
||||
<el-input v-model="splitRemark" :disabled="!canSplit || Boolean(busy)" class="workflow-remark" type="textarea" :rows="2" maxlength="1000" show-word-limit aria-label="分柜备注" placeholder="分柜备注(可选)" />
|
||||
@@ -533,8 +529,9 @@ onMounted(() => { void refresh() })
|
||||
.workflow-panel { display: grid; gap: 14px; }
|
||||
.workflow-heading, .card-title, .workflow-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||
.workflow-heading { justify-content: space-between; }
|
||||
.workflow-heading h2 { margin: 0; font-size: 17px; }
|
||||
.workflow-heading p { margin: 4px 0 0; color: var(--cc-text-secondary, #64748b); font-size: 13px; }
|
||||
.workflow-context { display: grid; min-width: 0; gap: 3px; }
|
||||
.workflow-context span { color: var(--cc-text-secondary, #64748b); font-size: 11px; }
|
||||
.workflow-context strong { overflow: hidden; color: var(--cc-text, #1f2937); font-size: 14px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.workflow-card { border-radius: 12px; }
|
||||
.card-title { justify-content: space-between; }
|
||||
.preload-dialog-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRoute } from 'vue-router'
|
||||
import { getPlatformUnifiedOrder, type UnifiedOrderDetailResource } from '@/modules/unified-orders/api/unifiedOrders'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import UnifiedOrderWorkflowPanel from '@/modules/unified-orders/workflows/components/UnifiedOrderWorkflowPanel.vue'
|
||||
|
||||
@@ -37,6 +38,7 @@ onMounted(() => { void load() })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage>
|
||||
<section class="workflow-page">
|
||||
<LoadErrorState v-if="error" title="后半程作业暂时无法加载" :message="error.message" :loading="loading" @retry="load">
|
||||
<template #details><ApiErrorMeta :details="error" /></template>
|
||||
@@ -54,6 +56,7 @@ onMounted(() => { void load() })
|
||||
<el-empty v-else-if="!loading" description="该订单尚未生成入库作业,暂无后半程操作" />
|
||||
<el-skeleton v-else :rows="5" animated />
|
||||
</section>
|
||||
</AdminPage>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user