优化
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox, type UploadFile } from 'element-plus'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
@@ -43,7 +44,7 @@ async function load() {
|
||||
async function reload() {
|
||||
if (busy.value) return
|
||||
if (snapshot.value && canEdit.value) {
|
||||
try { await ElMessageBox.confirm('重新加载将放弃本次未保存的资料修改,是否继续?', '重新加载资料', { type: 'warning' }) } catch { return }
|
||||
try { await ElMessageBox.confirm('重新加载将放弃本次未保存的资料修改,是否继续?', '重新加载资料', { customClass: 'admin-density-confirm', type: 'warning' }) } catch { return }
|
||||
}
|
||||
await load()
|
||||
}
|
||||
@@ -103,7 +104,7 @@ async function save() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppDialog v-model="open" title="派送单所需资料" width="min(1040px, calc(100vw - 40px))" confirm-text="保存资料" cancel-text="关闭" :loading="loading" :show-confirm="canEdit" :confirm-loading="busy" :confirm-disabled="conflict || loading" @confirm="save">
|
||||
<AppDialog class="admin-density-dialog" v-model="open" title="派送单所需资料" width="min(1040px, calc(100vw - 40px))" confirm-text="保存资料" cancel-text="关闭" :loading="loading" :show-confirm="canEdit" :confirm-loading="busy" :confirm-disabled="conflict || loading" @confirm="save">
|
||||
<div class="delivery-documents">
|
||||
<div class="document-identifiers"><label>派送单号<el-input :model-value="deliveryNo" readonly /></label><label>BOL号<el-input v-model="bolNo" :readonly="!canEdit" :disabled="busy || conflict" placeholder="未填写" /></label></div>
|
||||
<LoadErrorState v-if="error" compact title="派送资料操作失败" :message="error.message" :show-retry="!snapshot || conflict" @retry="reload"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
||||
@@ -111,11 +112,11 @@ async function save() {
|
||||
<div class="document-summary"><span>{{ canEdit ? '上传、替换和删除将在保存资料后生效;关闭将放弃未保存修改。' : '当前账号仅可查看已保存资料。' }}</span><strong>{{ uploadedCount }}/3 已上传</strong></div>
|
||||
<div class="document-groups">
|
||||
<section v-for="group in draft" :key="group.category" class="document-group" :data-category="group.category">
|
||||
<header><strong>{{ group.label }}</strong><el-tag :type="group.files.length ? 'success' : 'info'">{{ group.files.length ? '已上传' : '待上传' }}</el-tag><span>{{ group.files.length }}/20</span><el-upload v-if="canEdit" :key="uploadKey" :auto-upload="false" :show-file-list="false" :disabled="busy || conflict || group.files.length >= 20" :on-change="(file: UploadFile) => upload(group.category, file)"><el-button :disabled="busy || conflict || group.files.length >= 20">上传</el-button></el-upload></header>
|
||||
<header><strong>{{ group.label }}</strong><el-tag :type="group.files.length ? 'success' : 'info'">{{ group.files.length ? '已上传' : '待上传' }}</el-tag><span>{{ group.files.length }}/20</span><el-upload v-if="canEdit" :key="uploadKey" :auto-upload="false" :show-file-list="false" :disabled="busy || conflict || group.files.length >= 20" :on-change="(file: UploadFile) => upload(group.category, file)"><el-button class="density-action" data-tone="primary" :disabled="busy || conflict || group.files.length >= 20">上传</el-button></el-upload></header>
|
||||
<p v-if="!group.files.length" class="document-empty">尚未上传文件</p>
|
||||
<div v-for="file in group.files" :key="file.fileId" class="document-file">
|
||||
<div class="document-file-info"><button type="button" class="document-file-name" :title="file.fileName" :disabled="downloading || !isSaved(group.category, file.fileId)" @click="download(group.category, file)">{{ file.fileName }}</button><small>{{ file.uploadedAt?.replace('T', ' ') || '—' }}{{ isSaved(group.category, file.fileId) ? '' : ' · 待保存' }}</small></div>
|
||||
<template v-if="canEdit"><el-upload :key="uploadKey" :auto-upload="false" :show-file-list="false" :disabled="busy || conflict" :on-change="(uploadFile: UploadFile) => upload(group.category, uploadFile, file.fileId)"><el-button :disabled="busy || conflict">替换</el-button></el-upload><el-button type="danger" link :disabled="busy || conflict" @click="remove(group.category, file.fileId)">删除</el-button></template>
|
||||
<template v-if="canEdit"><el-upload :key="uploadKey" :auto-upload="false" :show-file-list="false" :disabled="busy || conflict" :on-change="(uploadFile: UploadFile) => upload(group.category, uploadFile, file.fileId)"><el-button class="density-action" data-tone="primary" :disabled="busy || conflict">替换</el-button></el-upload><el-button class="density-action" data-tone="danger" :disabled="busy || conflict" @click="remove(group.category, file.fileId)">删除</el-button></template>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -125,10 +126,10 @@ async function save() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.delivery-documents { display:grid; gap:16px; max-height:66vh; overflow-y:auto; padding:2px 6px 4px 0; }
|
||||
.document-identifiers { display:grid; grid-template-columns:1fr 1fr; gap:18px; }.document-identifiers label { display:grid; gap:8px; font-weight:600; }
|
||||
.document-summary { display:flex; justify-content:space-between; align-items:center; gap:20px; padding:12px 16px; background:var(--el-color-primary-light-9); color:var(--el-color-primary); border-radius:8px; }.document-summary strong { white-space:nowrap; }
|
||||
.document-groups { border:1px solid var(--cc-border); border-radius:8px; overflow:hidden; }.document-group { padding:14px 16px; }.document-group + .document-group { border-top:1px solid var(--cc-border); }
|
||||
.document-group header { display:flex; align-items:center; gap:10px; }.document-group header > span { color:var(--cc-muted); font-size:12px; }.document-group header :deep(.el-upload) { margin-left:auto; }.document-group header > :last-child { margin-left:auto; }
|
||||
.document-file { display:flex; align-items:center; gap:14px; padding-top:10px; }.document-file-info { display:grid; min-width:0; flex:1; gap:4px; }.document-file-name { border:0; background:none; padding:0; text-align:left; color:var(--cc-primary); overflow:hidden; white-space:nowrap; text-overflow:ellipsis; cursor:pointer; font:inherit; }.document-file-name:disabled { color:var(--cc-muted); cursor:default; }.document-file-info small,.document-empty { color:var(--cc-muted); }.document-empty { margin:10px 0 0; }
|
||||
.delivery-documents { display:grid; gap:var(--admin-space-5); padding:2px var(--admin-space-2) var(--admin-space-1) 0; }
|
||||
.document-identifiers { display:grid; grid-template-columns:1fr 1fr; gap:var(--admin-space-5); }.document-identifiers label { display:grid; gap:var(--admin-space-3); font-weight:600; }
|
||||
.document-summary { display:flex; justify-content:space-between; align-items:center; gap:var(--admin-space-5); padding:var(--admin-space-5) var(--admin-space-5); background:var(--el-color-primary-light-9); color:var(--el-color-primary); border-radius:var(--admin-radius); }.document-summary strong { white-space:nowrap; }
|
||||
.document-groups { border:1px solid var(--cc-border); border-radius:var(--admin-radius); overflow:hidden; }.document-group { padding:var(--admin-space-5) var(--admin-space-5); }.document-group + .document-group { border-top:1px solid var(--cc-border); }
|
||||
.document-group header { display:flex; align-items:center; gap:var(--admin-space-4); }.document-group header > span { color:var(--cc-muted); font-size:var(--admin-font-caption); }.document-group header :deep(.el-upload) { margin-left:auto; }.document-group header > :last-child { margin-left:auto; }
|
||||
.document-file { display:flex; align-items:center; gap:var(--admin-space-5); padding-top:var(--admin-space-4); }.document-file-info { display:grid; min-width:0; flex:1; gap:var(--admin-space-1); }.document-file-name { border:0; background:none; padding:0; text-align:left; color:var(--cc-primary); overflow:hidden; white-space:nowrap; text-overflow:ellipsis; cursor:pointer; font:inherit; }.document-file-name:disabled { color:var(--cc-muted); cursor:default; }.document-file-info small,.document-empty { color:var(--cc-muted); }.document-empty { margin:var(--admin-space-4) 0 0; }
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { ArrowLeft } from '@lucide/vue'
|
||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||
import ContractPendingNotice from './ContractPendingNotice.vue'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
title: string
|
||||
dense?: boolean
|
||||
referenceLabel: string
|
||||
referenceValue: string
|
||||
backLabel: string
|
||||
@@ -17,9 +19,9 @@ defineEmits<{ back: [] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AdminPage as="div" class="overseas-detail-page">
|
||||
<AdminPage as="div" class="overseas-detail-page" :class="{ 'admin-density': dense }">
|
||||
<header class="overseas-detail-head">
|
||||
<el-button :aria-label="backLabel" @click="$emit('back')"><ArrowLeft :size="16" />返回</el-button>
|
||||
<el-button :class="{ 'density-back': dense }" :link="dense" :aria-label="backLabel" @click="$emit('back')"><ArrowLeft :size="16" />返回</el-button>
|
||||
<div class="overseas-detail-title">
|
||||
<h1>{{ title }}</h1>
|
||||
<p>{{ referenceLabel }} <strong><slot name="reference">{{ referenceValue }}</slot></strong></p>
|
||||
@@ -41,4 +43,11 @@ defineEmits<{ back: [] }>()
|
||||
.overseas-detail-title strong { min-width: 0; max-width: 100%; color: var(--cc-text); }
|
||||
.overseas-detail-status { display: inline-flex; min-height: 26px; align-items: center; padding: 3px 10px; border: 1px solid #f3d19e; border-radius: 999px; background: #fdf6ec; color: #b88230; font-size: 12px; font-weight: 600; white-space: nowrap; }
|
||||
.overseas-detail-status.is-live { border-color: var(--el-color-success-light-7); background: var(--el-color-success-light-9); color: var(--el-color-success-dark-2); }
|
||||
.overseas-detail-page.admin-density { background: var(--admin-surface); border: 1px solid var(--cc-border); gap: 0; }
|
||||
.admin-density .overseas-detail-head { padding: var(--admin-space-3) var(--admin-space-5); gap: var(--admin-space-5); border-bottom: 1px solid var(--cc-border); flex-wrap: wrap; }
|
||||
.admin-density .overseas-detail-title { display: flex; flex: 0 1 auto; align-items: baseline; flex-wrap: wrap; gap: var(--admin-space-5); }
|
||||
.admin-density .overseas-detail-title h1 { font-size: var(--admin-font-section); font-weight: 500; }
|
||||
.admin-density .overseas-detail-title p { margin: 0; font-size: var(--admin-font-caption); }
|
||||
.admin-density .overseas-detail-title strong { font-size: var(--admin-font-title); overflow-wrap: anywhere; }
|
||||
.admin-density .overseas-detail-status { font-size: var(--admin-font-caption); }
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import OverseasDetailShell from './OverseasDetailShell.vue'
|
||||
|
||||
interface DetailGroup {
|
||||
@@ -7,6 +8,7 @@ interface DetailGroup {
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
dense?: boolean
|
||||
title: string
|
||||
referenceLabel: string
|
||||
referenceValue: string
|
||||
@@ -25,6 +27,8 @@ defineEmits<{ back: [] }>()
|
||||
|
||||
<template>
|
||||
<OverseasDetailShell
|
||||
:dense="dense"
|
||||
:class="{ 'admin-density': dense }"
|
||||
:title="title"
|
||||
:reference-label="referenceLabel"
|
||||
:reference-value="referenceValue"
|
||||
@@ -38,7 +42,7 @@ defineEmits<{ back: [] }>()
|
||||
<section v-if="showContent !== false" v-loading="loading" class="read-only-detail" :aria-label="title" :aria-busy="loading">
|
||||
<section v-for="group in groups" :key="group.title" class="detail-group" :aria-label="group.title">
|
||||
<h2>{{ group.title }}</h2>
|
||||
<dl class="detail-fields"><div v-for="field in group.fields" :key="field"><dt>{{ field }}</dt><dd>{{ fieldValues?.[field] ?? '—' }}</dd></div></dl>
|
||||
<dl class="detail-fields" :class="{ 'density-fields': dense }"><div v-for="field in group.fields" :key="field"><dt>{{ field }}</dt><dd>{{ fieldValues?.[field] ?? '—' }}</dd></div></dl>
|
||||
</section>
|
||||
<slot name="content-after" />
|
||||
</section>
|
||||
@@ -50,10 +54,16 @@ defineEmits<{ back: [] }>()
|
||||
.detail-group { min-width: 0; padding: 16px 18px 18px; border-right: 1px solid var(--cc-border); border-bottom: 1px solid var(--cc-border); }
|
||||
.detail-group:nth-child(even) { border-right: 0; }
|
||||
.detail-group h2 { margin: 0 0 12px; color: var(--cc-text); font-size: 15px; }
|
||||
.detail-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 0; border-top: 1px solid var(--cc-border); border-left: 1px solid var(--cc-border); }
|
||||
.detail-fields > div { display: grid; min-width: 0; min-height: 46px; grid-template-columns: 126px minmax(0, 1fr); border-right: 1px solid var(--cc-border); border-bottom: 1px solid var(--cc-border); }
|
||||
.detail-fields dt, .detail-fields dd { display: flex; min-width: 0; align-items: center; padding: 8px 10px; }
|
||||
.detail-fields dt { background: #f8fafc; color: var(--cc-muted); font-size: 12px; }
|
||||
.detail-fields dd { margin: 0; color: var(--cc-text); overflow-wrap: anywhere; }
|
||||
@media (max-width: 1100px) { .read-only-detail, .detail-fields { grid-template-columns: 1fr; } .detail-group { border-right: 0; } }
|
||||
.detail-fields:not(.density-fields) { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 0; border-top: 1px solid var(--cc-border); border-left: 1px solid var(--cc-border); }
|
||||
.detail-fields:not(.density-fields) > div { display: grid; min-width: 0; min-height: 46px; grid-template-columns: 126px minmax(0, 1fr); border-right: 1px solid var(--cc-border); border-bottom: 1px solid var(--cc-border); }
|
||||
.detail-fields:not(.density-fields) dt, .detail-fields:not(.density-fields) dd { display: flex; min-width: 0; align-items: center; padding: 8px 10px; }
|
||||
.detail-fields:not(.density-fields) dt { background: #f8fafc; color: var(--cc-muted); font-size: 12px; }
|
||||
.detail-fields:not(.density-fields) dd { margin: 0; color: var(--cc-text); overflow-wrap: anywhere; }
|
||||
@media (max-width: 1100px) { .read-only-detail, .detail-fields:not(.density-fields) { grid-template-columns: 1fr; } .detail-group { border-right: 0; } }
|
||||
|
||||
.admin-density .read-only-detail { grid-template-columns: minmax(0, 1fr); border: 0; }
|
||||
.admin-density .detail-group { padding: var(--admin-space-5); border-right: 0; }
|
||||
.admin-density .detail-group h2 { margin-bottom: var(--admin-space-3); font-size: var(--admin-font-section); }
|
||||
@media(max-width:1100px) { .admin-density .density-fields { grid-template-columns: 1fr; } }
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { ArrowRight, GripVertical, Plus, Trash2 } from '@lucide/vue'
|
||||
import { GripVertical, Plus } from '@lucide/vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
@@ -391,7 +392,7 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
|
||||
<template>
|
||||
<OperationErrorToast :details="mutationError" />
|
||||
<AppDialog :model-value="modelValue" title="智能排车" width="min(1800px, calc(100vw - 40px))" :show-footer="false" @update:model-value="close($event)">
|
||||
<AppDialog class="admin-density-dialog admin-density" :model-value="modelValue" title="智能排车" width="min(1800px, calc(100vw - 40px))" @update:model-value="close($event)">
|
||||
<section class="dispatch-query" aria-label="智能排车筛选">
|
||||
<el-input v-model="query.orderNo" clearable placeholder="订单号" @keyup.enter="load" />
|
||||
<el-select v-model="query.orderType" clearable placeholder="全部订单类型"><el-option v-for="option in orderTypeOptions" :key="option.value" :label="option.label" :value="option.value" /></el-select>
|
||||
@@ -400,24 +401,24 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
<el-button type="primary" :loading="loading" @click="load">查询</el-button><el-button :disabled="loading" @click="reset">重置</el-button>
|
||||
</section>
|
||||
|
||||
<nav class="warehouse-tabs" aria-label="海外仓筛选">
|
||||
<nav class="warehouse-tabs density-tabs" aria-label="海外仓筛选">
|
||||
<button
|
||||
v-for="tab in [
|
||||
v-for="tab in [
|
||||
{ value: 'ALL', label: '全部' },
|
||||
{ value: 'LAX', label: '洛杉矶' },
|
||||
{ value: 'NYC', label: '纽约' },
|
||||
]"
|
||||
:key="tab.value"
|
||||
type="button"
|
||||
:class="['warehouse-tab', { active: warehouseTab === tab.value }]"
|
||||
@click="warehouseTab = tab.value as 'ALL' | 'LAX' | 'NYC'"
|
||||
>
|
||||
:key="tab.value"
|
||||
type="button"
|
||||
:class="['warehouse-tab', { active: warehouseTab === tab.value }]"
|
||||
@click="warehouseTab = tab.value as 'ALL' | 'LAX' | 'NYC'"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<LoadErrorState v-if="loadError" title="可排车入仓单加载失败" :message="loadError.message" :loading="loading" @retry="load"
|
||||
><template #details><ApiErrorMeta :details="loadError" /></template
|
||||
><template #details><ApiErrorMeta :details="loadError" /></template
|
||||
></LoadErrorState>
|
||||
<div v-else class="dispatch-workbench">
|
||||
<section class="order-pane" aria-label="可排车入仓单">
|
||||
@@ -426,7 +427,7 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
<h3>
|
||||
出库单列表 <span>({{ orderGroups.length }}单)</span>
|
||||
</h3>
|
||||
<div class="order-type-tabs">
|
||||
<div class="order-type-tabs density-tabs">
|
||||
<button v-for="option in [{ value: '', label: '全部' }, ...orderTypeOptions]" :key="option.value || 'all'" type="button" :class="{ active: query.orderType === option.value }" @click="query.orderType = option.value">
|
||||
{{ option.label }}
|
||||
</button>
|
||||
@@ -436,28 +437,28 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
<div v-loading="loading" class="order-table-wrap">
|
||||
<el-table :data="orderGroups" border height="520" highlight-current-row row-key="key" @current-change="activeOrderKey = $event?.key || ''">
|
||||
<el-table-column label="订单号" min-width="170"
|
||||
><template #default="{ row }"><OrderReferenceText :value="text(row.first.orderNo)" /></template
|
||||
><template #default="{ row }"><OrderReferenceText :value="text(row.first.orderNo)" /></template
|
||||
></el-table-column>
|
||||
<el-table-column label="换标单号" min-width="180"
|
||||
><template #default="{ row }">{{ row.first.sourceStage === 'RELABEL' ? text(row.first.orderRefs) : '—' }}</template
|
||||
><template #default="{ row }">{{ row.first.sourceStage === 'RELABEL' ? text(row.first.orderRefs) : '—' }}</template
|
||||
></el-table-column>
|
||||
<el-table-column label="原入仓码" min-width="150"
|
||||
><template #default="{ row }">{{ text(row.first.inboundCode) }}</template
|
||||
><template #default="{ row }">{{ text(row.first.inboundCode) }}</template
|
||||
></el-table-column>
|
||||
<el-table-column label="订单类型" width="100"
|
||||
><template #default="{ row }">{{ orderType(row.first.orderType) }}</template></el-table-column
|
||||
><template #default="{ row }">{{ orderType(row.first.orderType) }}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="重量(lb)" width="100" align="right"
|
||||
><template #default="{ row }">{{ number(Number(row.first.volumeWeightKg || 0) * 2.2046226218, 0) }}LB</template></el-table-column
|
||||
><template #default="{ row }">{{ number(Number(row.first.volumeWeightKg || 0) * 2.2046226218, 0) }}LB</template></el-table-column
|
||||
>
|
||||
<el-table-column label="托盘数量" width="90" align="right"
|
||||
><template #default="{ row }">{{ row.pallets.length }}</template></el-table-column
|
||||
><template #default="{ row }">{{ row.pallets.length }}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="目的地FBA" width="110"
|
||||
><template #default="{ row }">{{ text(row.first.fbaWarehouseCode) }}</template></el-table-column
|
||||
><template #default="{ row }">{{ text(row.first.fbaWarehouseCode) }}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="体积(m³)" width="100" align="right"
|
||||
><template #default="{ row }">{{
|
||||
><template #default="{ row }">{{
|
||||
number(
|
||||
row.pallets.reduce((sum: number, item: DispatchPalletCandidateDto) => sum + Number(item.volumeCbm || 0), 0),
|
||||
2,
|
||||
@@ -465,13 +466,13 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
}}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="货柜号" min-width="125"
|
||||
><template #default="{ row }">{{ text(row.first.containerNo) }}</template></el-table-column
|
||||
><template #default="{ row }">{{ text(row.first.containerNo) }}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="预约送仓时间" min-width="145"
|
||||
><template #default="{ row }">{{ dateTime(row.first.appointmentAt) }}</template></el-table-column
|
||||
><template #default="{ row }">{{ dateTime(row.first.appointmentAt) }}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="预计装车时间" min-width="145"
|
||||
><template #default="{ row }">{{ dateTime(row.first.expectedLoadingAt) }}</template></el-table-column
|
||||
><template #default="{ row }">{{ dateTime(row.first.expectedLoadingAt) }}</template></el-table-column
|
||||
>
|
||||
</el-table>
|
||||
</div>
|
||||
@@ -500,10 +501,10 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
</div>
|
||||
<el-tag v-if="containerFor(palletId(item))" type="success" size="small">已锁定</el-tag>
|
||||
<el-dropdown v-else trigger="click" :disabled="!containers.length || busyPalletIds.includes(palletId(item))" @command="movePallet(item, String($event))">
|
||||
<el-button text type="primary" :loading="busyPalletIds.includes(palletId(item))">移入 <ArrowRight :size="14" /></el-button>
|
||||
<el-button class="density-action" data-tone="primary" :loading="busyPalletIds.includes(palletId(item))">移入 </el-button>
|
||||
<template #dropdown
|
||||
><el-dropdown-menu
|
||||
><el-dropdown-item v-for="(container, index) in containers" :key="container.id" :command="container.id" :disabled="container.palletIds.length >= container.capacity">装车托盘 {{ index + 1 }}({{ container.palletIds.length }}/{{ container.capacity }})</el-dropdown-item></el-dropdown-menu
|
||||
><el-dropdown-menu
|
||||
><el-dropdown-item v-for="(container, index) in containers" :key="container.id" :command="container.id" :disabled="container.palletIds.length >= container.capacity">装车托盘 {{ index + 1 }}({{ container.palletIds.length }}/{{ container.capacity }})</el-dropdown-item></el-dropdown-menu
|
||||
></template
|
||||
>
|
||||
</el-dropdown>
|
||||
@@ -525,28 +526,28 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
</header>
|
||||
<div class="container-list">
|
||||
<div
|
||||
v-for="(container, index) in containers"
|
||||
:key="container.id"
|
||||
class="loading-container"
|
||||
:class="{
|
||||
v-for="(container, index) in containers"
|
||||
:key="container.id"
|
||||
class="loading-container"
|
||||
:class="{
|
||||
'loading-container--overweight': containerWeightLb(container) > container.maxWeightLb * 1.1,
|
||||
}"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropInto($event, container.id)"
|
||||
>
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropInto($event, container.id)"
|
||||
>
|
||||
<div class="container-head">
|
||||
<strong>{{ container.vehicleLabel }}</strong
|
||||
><span
|
||||
>托数 <b>{{ container.palletIds.length }}</b
|
||||
>托数 <b>{{ container.palletIds.length }}</b
|
||||
>/{{ container.capacity }} · 重量 <b>{{ number(containerWeightLb(container), 0) }}LB</b>/{{ container.maxWeightLb }}LB <em v-if="containerWeightLb(container) > container.maxWeightLb * 1.1">超限10%</em></span
|
||||
><el-button text type="danger" :aria-label="`删除装车车型${index + 1}`" @click="deleteContainer(container)"><Trash2 :size="15" /></el-button>
|
||||
><el-button class="density-action" data-tone="danger" :aria-label="`删除装车车型${index + 1}`" @click="deleteContainer(container)">删除</el-button>
|
||||
</div>
|
||||
<div class="container-body">
|
||||
<div v-for="id in container.palletIds" :key="id" class="loaded-pallet">
|
||||
<span
|
||||
><strong>{{ text(palletSnapshot(id)?.palletNo || '托盘号待补充') }}</strong
|
||||
><strong>{{ text(palletSnapshot(id)?.palletNo || '托盘号待补充') }}</strong
|
||||
><OrderReferenceText :value="text(palletSnapshot(id)?.orderNo)" /></span
|
||||
><el-button text type="danger" :loading="busyPalletIds.includes(id)" @click="removePallet(id)">移除</el-button>
|
||||
><el-button class="density-action" data-tone="danger" :loading="busyPalletIds.includes(id)" @click="removePallet(id)">移除</el-button>
|
||||
</div>
|
||||
<span v-if="!container.palletIds.length" class="drop-hint">拖入托盘,移入后立即锁定</span>
|
||||
</div>
|
||||
@@ -556,12 +557,12 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<footer class="dialog-footer">
|
||||
<template #footer><footer class="dialog-footer">
|
||||
<span>托盘移入即锁定,重量按 1kg = 2.2046lb 换算。</span>
|
||||
<div><el-button :disabled="saving" @click="close(false)">取消</el-button><el-button type="primary" :loading="saving" :disabled="!assignedIds.length" @click="submit">预排车</el-button></div>
|
||||
</footer>
|
||||
</footer></template>
|
||||
</AppDialog>
|
||||
<el-dialog v-model="vehicleDialogOpen" title="选择车型" width="360px" append-to-body>
|
||||
<el-dialog class="admin-density-dialog" v-model="vehicleDialogOpen" title="选择车型" width="360px" append-to-body>
|
||||
<el-radio-group v-model="selectedVehicle" class="vehicle-options">
|
||||
<el-radio v-for="spec in vehicleSpecs" :key="spec.value" :value="spec.value">{{ spec.label }} · 限重 {{ spec.maxWeightLb }}LB</el-radio>
|
||||
</el-radio-group>
|
||||
@@ -573,10 +574,10 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
.dispatch-query {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
gap: var(--admin-space-5);
|
||||
padding: var(--admin-space-5) var(--admin-space-5);
|
||||
border: 1px solid #e6eaf2;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--admin-radius);
|
||||
background: #f7f8fb;
|
||||
}
|
||||
.dispatch-query :deep(.el-input),
|
||||
@@ -584,40 +585,13 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
width: 190px;
|
||||
}
|
||||
.warehouse-tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.warehouse-tab,
|
||||
.order-type-tabs button {
|
||||
border: 1px solid #dfe4ed;
|
||||
background: #fff;
|
||||
color: #667085;
|
||||
cursor: pointer;
|
||||
transition: all 0.18s ease;
|
||||
}
|
||||
.warehouse-tab {
|
||||
min-width: 92px;
|
||||
padding: 9px 20px;
|
||||
border-radius: 7px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.warehouse-tab:hover,
|
||||
.order-type-tabs button:hover {
|
||||
border-color: #7654d6;
|
||||
color: #6941c6;
|
||||
}
|
||||
.warehouse-tab.active,
|
||||
.order-type-tabs button.active {
|
||||
border-color: #6f42d8;
|
||||
background: #6f42d8;
|
||||
color: #fff;
|
||||
margin-top: var(--admin-space-5);
|
||||
}
|
||||
.dispatch-workbench {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(550px, 1.5fr) minmax(330px, 0.85fr) minmax(390px, 1fr);
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
gap: var(--admin-space-5);
|
||||
margin-top: var(--admin-space-5);
|
||||
}
|
||||
.order-pane,
|
||||
.detail-pane,
|
||||
@@ -631,7 +605,7 @@ watch([warehouseTab, () => query.orderType], () => {
|
||||
min-height: 74px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
gap: var(--admin-space-4);
|
||||
}
|
||||
.detail-order-reference {
|
||||
display: flex;
|
||||
@@ -645,26 +619,17 @@ p {
|
||||
margin: 0;
|
||||
}
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-size: var(--admin-font-section);
|
||||
}
|
||||
h3 span,
|
||||
header > span,
|
||||
p {
|
||||
color: var(--cc-muted);
|
||||
font-size: 12px;
|
||||
font-size: var(--admin-font-caption);
|
||||
font-weight: 400;
|
||||
}
|
||||
.order-type-tabs {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-top: 10px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.order-type-tabs button {
|
||||
flex: 0 0 auto;
|
||||
padding: 5px 12px;
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
margin-top: var(--admin-space-4);
|
||||
}
|
||||
.order-table-wrap {
|
||||
min-height: 520px;
|
||||
@@ -672,28 +637,16 @@ p {
|
||||
.detail-pane,
|
||||
.container-pane {
|
||||
border-left: 1px solid var(--cc-border);
|
||||
padding-left: 16px;
|
||||
padding-left: var(--admin-space-5);
|
||||
}
|
||||
.pallet-list,
|
||||
.container-list {
|
||||
height: 520px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--cc-border);
|
||||
border-radius: 6px;
|
||||
border-radius: var(--admin-radius);
|
||||
background: #fafbfc;
|
||||
}
|
||||
.pallet-row {
|
||||
display: grid;
|
||||
grid-template-columns: 20px minmax(0, 1fr) auto;
|
||||
min-height: 94px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--cc-border);
|
||||
background: #fff;
|
||||
cursor: grab;
|
||||
transition: background 0.18s ease;
|
||||
}
|
||||
.pallet-row:hover {
|
||||
background: #f8f6ff;
|
||||
}
|
||||
@@ -707,26 +660,26 @@ p {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: var(--admin-space-1);
|
||||
}
|
||||
.pallet-copy > span,
|
||||
.carton-marks > span {
|
||||
color: var(--cc-muted);
|
||||
font-size: 12px;
|
||||
font-size: var(--admin-font-caption);
|
||||
}
|
||||
.carton-marks {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: var(--admin-space-1);
|
||||
}
|
||||
.carton-mark-line {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: var(--admin-space-2);
|
||||
color: var(--cc-muted);
|
||||
font-size: 12px;
|
||||
font-size: var(--admin-font-caption);
|
||||
}
|
||||
.carton-mark-line > span {
|
||||
overflow: hidden;
|
||||
@@ -734,16 +687,16 @@ p {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.container-pane > .el-input {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: var(--admin-space-4);
|
||||
}
|
||||
.container-list {
|
||||
height: 460px;
|
||||
padding: 8px;
|
||||
padding: var(--admin-space-3);
|
||||
}
|
||||
.loading-container {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: var(--admin-space-4);
|
||||
border: 1px dashed #bcc6d5;
|
||||
border-radius: 6px;
|
||||
border-radius: var(--admin-radius);
|
||||
background: #fff;
|
||||
}
|
||||
.loading-container--overweight {
|
||||
@@ -757,44 +710,35 @@ p {
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
.container-head em {
|
||||
margin-left: 4px;
|
||||
margin-left: var(--admin-space-1);
|
||||
color: var(--el-color-danger);
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
.container-head {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 32px;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 8px 0 12px;
|
||||
background: #f4f6fa;
|
||||
}
|
||||
.container-head span {
|
||||
color: var(--cc-muted);
|
||||
font-size: 12px;
|
||||
font-size: var(--admin-font-caption);
|
||||
}
|
||||
.container-head b {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.container-body {
|
||||
min-height: 76px;
|
||||
padding: 8px;
|
||||
padding: var(--admin-space-3);
|
||||
}
|
||||
.drop-hint {
|
||||
display: grid;
|
||||
min-height: 60px;
|
||||
place-items: center;
|
||||
color: var(--cc-muted);
|
||||
font-size: 12px;
|
||||
font-size: var(--admin-font-caption);
|
||||
}
|
||||
.loaded-pallet {
|
||||
display: flex;
|
||||
min-height: 52px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
gap: var(--admin-space-3);
|
||||
border-bottom: 1px solid var(--cc-border);
|
||||
}
|
||||
.loaded-pallet > span {
|
||||
@@ -803,29 +747,19 @@ p {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
color: var(--cc-muted);
|
||||
font-size: 12px;
|
||||
font-size: var(--admin-font-caption);
|
||||
}
|
||||
.loaded-pallet strong {
|
||||
color: var(--cc-text);
|
||||
font-size: 13px;
|
||||
}
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin: 16px -20px -20px;
|
||||
padding: 14px 20px;
|
||||
border-top: 1px solid var(--cc-border);
|
||||
background: #fafbfc;
|
||||
font-size: var(--admin-font-body);
|
||||
}
|
||||
.dialog-footer > span {
|
||||
color: var(--cc-muted);
|
||||
font-size: 12px;
|
||||
font-size: var(--admin-font-caption);
|
||||
}
|
||||
.vehicle-options {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
gap: var(--admin-space-5);
|
||||
}
|
||||
@media (max-width: 1280px) {
|
||||
.dispatch-workbench {
|
||||
@@ -840,4 +774,30 @@ p {
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--admin-space-5); margin: 0; padding: 0;
|
||||
border-top: 1px solid var(--cc-border); background: transparent; border: 0; text-align: left; }
|
||||
.container-head {
|
||||
display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
gap: var(--admin-space-3);
|
||||
padding: 0 var(--admin-space-3) 0 var(--admin-space-5);
|
||||
background: #f4f6fa; }
|
||||
.pallet-row {
|
||||
display: grid;
|
||||
grid-template-columns: 20px minmax(0, 1fr) auto; min-height: var(--admin-empty-height);
|
||||
align-items: center;
|
||||
gap: var(--admin-space-3);
|
||||
padding: var(--admin-space-4) var(--admin-space-5);
|
||||
border-bottom: 1px solid var(--cc-border);
|
||||
background: #fff;
|
||||
cursor: grab;
|
||||
transition: background 0.18s ease; }
|
||||
.order-pane header, .detail-pane header, .container-pane header { min-height: 60px; }
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import '@/styles/adminDensity.css'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { CheckCircle2, XCircle } from '@lucide/vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
@@ -54,9 +55,9 @@ watch(() => props.modelValue, (value) => { if (value) { form.decision = 'APPROVE
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppDialog v-model="open" title="审核仓储转运单" width="min(920px, calc(100vw - 28px))" confirm-text="提交审核" :confirm-loading="saving" :loading="loading" @confirm="submit">
|
||||
<AppDialog class="admin-density-dialog" v-model="open" title="审核仓储转运单" width="min(920px, calc(100vw - 28px))" confirm-text="提交审核" :confirm-loading="saving" :loading="loading" @confirm="submit">
|
||||
<LoadErrorState v-if="error" compact title="审核资料加载失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
||||
<el-form v-else-if="detail" label-position="top" class="review-form">
|
||||
<el-form v-else-if="detail" label-position="top" class="review-form density-form">
|
||||
<div class="review-heading"><strong>{{ detail.transferNo }}</strong><span>{{ customerName }} · {{ detail.packageCount }} 箱 · {{ detail.needRelabel ? '需要换标' : '无需换标' }}</span></div>
|
||||
<h3>货物明细</h3><DataTable :data="detail.items" :columns="itemColumns" :show-pagination="false" row-key="id" empty-text="暂无货物明细" />
|
||||
<template v-if="detail.needRelabel"><h3>换标绑定明细</h3><DataTable :data="detail.relabelBindings" :columns="bindingColumns" :show-pagination="false" row-key="customerCartonMark" empty-text="暂无换标绑定明细" /></template>
|
||||
@@ -68,10 +69,10 @@ watch(() => props.modelValue, (value) => { if (value) { form.decision = 'APPROVE
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.review-form { display: grid; gap: 12px; }
|
||||
.review-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
|
||||
.review-heading span { color: var(--cc-muted); font-size: 12px; }
|
||||
h3 { margin: 2px 0 0; font-size: 14px; }
|
||||
.review-form :deep(.el-form-item) { margin: 4px 0 0; }
|
||||
.review-form :deep(.el-radio-button__inner) { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.review-form { display: grid; gap: var(--admin-space-5); }
|
||||
.review-heading { display: flex; align-items: baseline; justify-content: space-between; gap: var(--admin-space-5); }
|
||||
.review-heading span { color: var(--cc-muted); font-size: var(--admin-font-caption); }
|
||||
h3 { margin: 2px 0 0; font-size: var(--admin-font-section); }
|
||||
.review-form :deep(.el-form-item) { margin: var(--admin-space-1) 0 0; }
|
||||
.review-form :deep(.el-radio-button__inner) { display: inline-flex; align-items: center; gap: var(--admin-space-2); }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user