fix: 抽屉去顶栏/圆角,订单操作按钮统一到 Tab 下

DrawerPro 默认隐藏空 header;抽屉无圆角;修复全部轨迹开关;详情操作从 footer 挪到 Tab 工具栏。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-09-03 18:05:14 +08:00
parent a703d1efdd
commit f71d2f1088
7 changed files with 257 additions and 139 deletions

View File

@ -135,13 +135,13 @@ body .arco-modal-simple .arco-modal-title {
text-align: left;
}
/* Rounded corners to align with SimpleAdmin (Element Plus-like 4px) */
/* Rounded corners to align with SimpleAdmin (Element Plus-like 4px)
抽屉贴边展开,不做圆角 */
body .arco-card,
body .arco-card-bordered,
body .arco-modal,
body .arco-popover-content,
body .arco-dropdown,
body .arco-drawer,
body .arco-input-wrapper,
body .arco-textarea-wrapper,
body .arco-select,
@ -155,6 +155,10 @@ body .arco-notification {
border-radius: 8px !important;
}
body .arco-drawer {
border-radius: 0 !important;
}
/* vxe table wrapper */
body .vxe-table--border,
body .vxe-grid {

View File

@ -10,11 +10,18 @@
visible: false,
width: 400,
placement: 'right',
// 默认不展示顶栏(可点遮罩关闭);需要标题时 setDrawerProps({ title }) 会自动打开
header: false,
...unref(Attrs),
});
const setDrawerProps = (props: DrawerProps) => {
Object.assign(defaultProps, props);
const next: DrawerProps = { ...props };
// 传入 title 且未显式指定 header 时,自动显示顶栏
if (next.title && next.header === undefined) {
next.header = true;
}
Object.assign(defaultProps, next);
};
const instance = getCurrentInstance();

View File

@ -1,25 +1,10 @@
<template>
<DrawerPro
:footer="false"
padding-bottom="16px"
@register="register"
@cancel="handleCancel"
>
<template #title />
<template #footer>
<div class="drawer-footer-banner order-detail-drawer-shell">
<template v-if="activeTab === 'basic'">
<template v-if="basicEditing">
<a-button class="app-btn-search" @click="cancelBasicEdit">{{ $t('order.myOrders.drawer.actions.cancel') }}</a-button>
<a-button class="app-btn-search" :loading="basicSaving" @click="handleBasicEditSave">
{{ $t('order.myOrders.drawer.actions.save') }}
</a-button>
</template>
<a-button v-else class="app-btn-search" @click="startBasicEdit">
{{ $t('order.myOrders.drawer.actions.edit') }}
</a-button>
</template>
</div>
</template>
<div class="order-detail-drawer order-detail-drawer-shell">
<div class="drawer-tabs">
<a-tabs
@ -36,6 +21,48 @@
</a-tabs>
</div>
<div
v-if="activeTab === 'basic' || activeTab === 'track' || activeTab === 'stockBind'"
class="drawer-tab-toolbar"
>
<div class="toolbar-left">
<template v-if="activeTab === 'basic'">
<template v-if="basicEditing">
<a-button class="app-btn-search" @click="cancelBasicEdit">{{ $t('order.myOrders.drawer.actions.cancel') }}</a-button>
<a-button class="app-btn-search" :loading="basicSaving" @click="handleBasicEditSave">
{{ $t('order.myOrders.drawer.actions.save') }}
</a-button>
</template>
<a-button v-else class="app-btn-search" @click="startBasicEdit">
{{ $t('order.myOrders.drawer.actions.edit') }}
</a-button>
</template>
<template v-else-if="activeTab === 'track'">
<span class="toolbar-meta">
<span class="toolbar-meta-label">{{ $t('order.myOrders.drawer.trackNo') }}</span>
<span class="toolbar-meta-value">{{ trackOrderNumber || '--' }}</span>
</span>
<a-tag v-if="trackTimeline.length" size="small" color="arcoblue">
{{ $t('order.myOrders.drawer.track.count', { count: trackTimeline.length }) }}
</a-tag>
<a-button
class="app-btn-search"
:loading="trackLoading"
@click="loadTrackTimeline"
>{{ $t('order.myOrders.drawer.track.refresh') }}
</a-button>
</template>
<template v-else-if="activeTab === 'stockBind'">
<a-button
class="app-btn-search"
:loading="stockBindLoading"
@click="reloadStockBind"
>{{ $t('order.myOrders.drawer.track.refresh') }}
</a-button>
</template>
</div>
</div>
<div class="drawer-tab-content">
<!-- 基本信息 -->
<div v-if="activeTab === 'basic'" class="tab-panel basic-info">
@ -544,24 +571,6 @@
<!-- 轨迹 -->
<div v-if="activeTab === 'track'" class="tab-panel track-info">
<div class="track-toolbar">
<div class="track-toolbar-left">
<span class="track-toolbar-label">{{ $t('order.myOrders.drawer.trackNo') }}</span>
<span class="track-toolbar-value">{{ trackOrderNumber || '--' }}</span>
</div>
<div class="track-toolbar-right">
<a-tag v-if="trackTimeline.length" size="small" color="arcoblue">
{{ $t('order.myOrders.drawer.track.count', { count: trackTimeline.length }) }}
</a-tag>
<a-button
class="app-btn-search"
:loading="trackLoading"
@click="loadTrackTimeline"
>{{ $t('order.myOrders.drawer.track.refresh') }}
</a-button>
</div>
</div>
<a-spin :loading="trackLoading" class="track-spin">
<div v-if="displayTrackTimeline.length" class="track-timeline">
<div
@ -631,8 +640,10 @@
<!-- 绑定包裹/库存 -->
<div v-if="activeTab === 'stockBind'" class="tab-panel stock-bind-info">
<OrderStockBindTab
ref="stockBindTabRef"
:order-id="orderId"
:active="activeTab === 'stockBind'"
hide-refresh
/>
</div>
</div>
@ -723,6 +734,17 @@ const recalcReceivableLoading = ref(false);
const recalcPayableLoading = ref(false);
const addFeeModalVisible = ref(false);
const addFeeDefaultType = ref<'receivable' | 'payable'>('receivable');
const stockBindTabRef = ref<{ reload?: () => Promise<void> | void } | null>(null);
const stockBindLoading = ref(false);
async function reloadStockBind() {
stockBindLoading.value = true;
try {
await stockBindTabRef.value?.reload?.();
} finally {
stockBindLoading.value = false;
}
}
type BasicViewMode = 'all' | 'basic' | 'sender' | 'recipient' | 'declaration';
const basicViewMode = ref<BasicViewMode>('all');
@ -1982,10 +2004,44 @@ defineExpose({
}
.drawer-tabs {
margin-bottom: 16px;
margin-bottom: 12px;
flex-shrink: 0;
}
.drawer-tab-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-shrink: 0;
.toolbar-left {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.toolbar-meta {
display: inline-flex;
align-items: baseline;
gap: 6px;
margin-right: 4px;
}
.toolbar-meta-label {
font-size: 13px;
color: var(--color-text-3);
}
.toolbar-meta-value {
font-size: 13px;
font-weight: 500;
color: var(--color-text-1);
}
}
.drawer-tab-content {
flex: 1;
overflow-y: auto;
@ -1997,14 +2053,6 @@ defineExpose({
padding: 0 4px;
}
.drawer-footer-banner {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 8px;
width: 100%;
}
.inline-edit-form {
:deep(.arco-form-item) {
margin-bottom: 12px;

View File

@ -8,6 +8,8 @@ import { resolveOmsAssetUrl } from '@/api/oms/common'
const props = defineProps<{
orderId: number | null | undefined
active: boolean
/** 刷新按钮已上移到抽屉 Tab 工具栏时隐藏内部刷新 */
hideRefresh?: boolean
}>()
const { t } = useI18n()
@ -113,7 +115,13 @@ defineExpose({ reload: loadDetail })
{{ t('order.myOrders.stockBind.cartSummary', { lines: items.length, qty: totalBindQty }) }}
</span>
</div>
<a-button type="text" size="small" :loading="loading" @click="loadDetail">{{ t('order.myOrders.drawer.track.refresh') }}
<a-button
v-if="!props.hideRefresh"
type="text"
size="small"
:loading="loading"
@click="loadDetail"
>{{ t('order.myOrders.drawer.track.refresh') }}
</a-button>
</div>

View File

@ -1,63 +1,10 @@
<template>
<DrawerPro
:footer="false"
padding-bottom="16px"
@register="register"
@cancel="handleCancel"
>
<template #title />
<template #footer>
<div class="drawer-footer-banner order-detail-drawer-shell">
<template v-if="activeTab === 'basic'">
<template v-if="basicEditing">
<a-button class="app-btn-search" @click="cancelBasicEdit">{{ $t('order.myOrders.drawer.actions.cancel') }}</a-button>
<a-button class="app-btn-search" :loading="basicSaving" @click="handleBasicEditSave">
{{ $t('order.myOrders.drawer.actions.save') }}
</a-button>
</template>
<a-button v-else class="app-btn-search" @click="startBasicEdit">
{{ $t('order.myOrders.drawer.actions.edit') }}
</a-button>
</template>
<template v-else-if="activeTab === 'weight'">
<template v-if="weightEditing">
<a-button class="app-btn-search" @click="cancelWeightEdit">{{ $t('order.myOrders.drawer.actions.cancel') }}</a-button>
<a-button class="app-btn-search" :loading="weightSaving" @click="handleWeightEditSave">
{{ $t('order.myOrders.drawer.actions.save') }}
</a-button>
</template>
<a-button v-else class="app-btn-search" @click="startWeightEdit">
{{ $t('order.myOrders.drawer.actions.edit') }}
</a-button>
</template>
<template v-else-if="activeTab === 'fee'">
<a-button
v-if="canEditAdditionalServices"
class="app-btn-search"
:loading="additionalServicesLoading"
@click="openAdditionalServicesModal"
>
{{ $t('order.myOrders.drawer.actions.additionalServices') }}
</a-button>
<a-button class="app-btn-search" @click="openAddFeeModal">
{{ $t('order.myOrders.drawer.actions.addFee') }}
</a-button>
<a-button
class="app-btn-search"
:loading="recalcReceivableLoading"
@click="handleRecalculateReceivable"
>
{{ $t('order.myOrders.drawer.actions.recalculateReceivable') }}
</a-button>
<a-button
class="app-btn-search"
:loading="recalcPayableLoading"
@click="handleRecalculatePayable"
>
{{ $t('order.myOrders.drawer.actions.recalculatePayable') }}
</a-button>
</template>
</div>
</template>
<div class="order-detail-drawer order-detail-drawer-shell">
<div class="drawer-tabs">
<a-tabs
@ -74,6 +21,86 @@
</a-tabs>
</div>
<div
v-if="activeTab === 'basic' || activeTab === 'weight' || activeTab === 'fee' || activeTab === 'track' || activeTab === 'stockBind'"
class="drawer-tab-toolbar"
>
<div class="toolbar-left">
<template v-if="activeTab === 'basic'">
<template v-if="basicEditing">
<a-button class="app-btn-search" @click="cancelBasicEdit">{{ $t('order.myOrders.drawer.actions.cancel') }}</a-button>
<a-button class="app-btn-search" :loading="basicSaving" @click="handleBasicEditSave">
{{ $t('order.myOrders.drawer.actions.save') }}
</a-button>
</template>
<a-button v-else class="app-btn-search" @click="startBasicEdit">
{{ $t('order.myOrders.drawer.actions.edit') }}
</a-button>
</template>
<template v-else-if="activeTab === 'weight'">
<template v-if="weightEditing">
<a-button class="app-btn-search" @click="cancelWeightEdit">{{ $t('order.myOrders.drawer.actions.cancel') }}</a-button>
<a-button class="app-btn-search" :loading="weightSaving" @click="handleWeightEditSave">
{{ $t('order.myOrders.drawer.actions.save') }}
</a-button>
</template>
<a-button v-else class="app-btn-search" @click="startWeightEdit">
{{ $t('order.myOrders.drawer.actions.edit') }}
</a-button>
</template>
<template v-else-if="activeTab === 'fee'">
<a-button
v-if="canEditAdditionalServices"
class="app-btn-search"
:loading="additionalServicesLoading"
@click="openAdditionalServicesModal"
>
{{ $t('order.myOrders.drawer.actions.additionalServices') }}
</a-button>
<a-button class="app-btn-search" @click="openAddFeeModal">
{{ $t('order.myOrders.drawer.actions.addFee') }}
</a-button>
<a-button
class="app-btn-search"
:loading="recalcReceivableLoading"
@click="handleRecalculateReceivable"
>
{{ $t('order.myOrders.drawer.actions.recalculateReceivable') }}
</a-button>
<a-button
class="app-btn-search"
:loading="recalcPayableLoading"
@click="handleRecalculatePayable"
>
{{ $t('order.myOrders.drawer.actions.recalculatePayable') }}
</a-button>
</template>
<template v-else-if="activeTab === 'track'">
<span class="toolbar-meta">
<span class="toolbar-meta-label">{{ $t('order.myOrders.drawer.trackNo') }}</span>
<span class="toolbar-meta-value">{{ trackOrderNumber || '--' }}</span>
</span>
<a-tag v-if="trackTimeline.length" size="small" color="arcoblue">
{{ $t('order.myOrders.drawer.track.count', { count: trackTimeline.length }) }}
</a-tag>
<a-button
class="app-btn-search"
:loading="trackLoading"
@click="loadTrackTimeline"
>{{ $t('order.myOrders.drawer.track.refresh') }}
</a-button>
</template>
<template v-else-if="activeTab === 'stockBind'">
<a-button
class="app-btn-search"
:loading="stockBindLoading"
@click="reloadStockBind"
>{{ $t('order.myOrders.drawer.track.refresh') }}
</a-button>
</template>
</div>
</div>
<div class="drawer-tab-content">
<!-- 基本信息 -->
<div v-if="activeTab === 'basic'" class="tab-panel basic-info">
@ -737,24 +764,6 @@
<!-- 轨迹 -->
<div v-if="activeTab === 'track'" class="tab-panel track-info">
<div class="track-toolbar">
<div class="track-toolbar-left">
<span class="track-toolbar-label">{{ $t('order.myOrders.drawer.trackNo') }}</span>
<span class="track-toolbar-value">{{ trackOrderNumber || '--' }}</span>
</div>
<div class="track-toolbar-right">
<a-tag v-if="trackTimeline.length" size="small" color="arcoblue">
{{ $t('order.myOrders.drawer.track.count', { count: trackTimeline.length }) }}
</a-tag>
<a-button
class="app-btn-search"
:loading="trackLoading"
@click="loadTrackTimeline"
>{{ $t('order.myOrders.drawer.track.refresh') }}
</a-button>
</div>
</div>
<a-spin :loading="trackLoading" class="track-spin">
<div v-if="displayTrackTimeline.length" class="track-timeline">
<div
@ -822,8 +831,10 @@
<!-- 绑定包裹/库存 -->
<div v-if="activeTab === 'stockBind'" class="tab-panel stock-bind-info">
<OrderStockBindTab
ref="stockBindTabRef"
:order-id="orderId"
:active="activeTab === 'stockBind'"
hide-refresh
/>
</div>
@ -958,6 +969,17 @@ const selectedAdditionalServices = ref<string[]>([]);
const productHasAdditionalServices = ref(false);
const additionalServiceDictRows = ref<DictDto[]>([]);
const exportCustomsDictRows = ref<DictDto[]>([]);
const stockBindTabRef = ref<{ reload?: () => Promise<void> | void } | null>(null);
const stockBindLoading = ref(false);
async function reloadStockBind() {
stockBindLoading.value = true;
try {
await stockBindTabRef.value?.reload?.();
} finally {
stockBindLoading.value = false;
}
}
type BasicViewMode = 'all' | 'basic' | 'sender' | 'recipient' | 'declaration';
const basicViewMode = ref<BasicViewMode>('all');
@ -2380,10 +2402,44 @@ defineExpose({
}
.drawer-tabs {
margin-bottom: 16px;
margin-bottom: 12px;
flex-shrink: 0;
}
.drawer-tab-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-shrink: 0;
.toolbar-left {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.toolbar-meta {
display: inline-flex;
align-items: baseline;
gap: 6px;
margin-right: 4px;
}
.toolbar-meta-label {
font-size: 13px;
color: var(--color-text-3);
}
.toolbar-meta-value {
font-size: 13px;
font-weight: 500;
color: var(--color-text-1);
}
}
.drawer-tab-content {
flex: 1;
overflow-y: auto;
@ -2395,14 +2451,6 @@ defineExpose({
padding: 0 4px;
}
.drawer-footer-banner {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 8px;
width: 100%;
}
.inline-edit-form {
:deep(.arco-form-item) {
margin-bottom: 12px;

View File

@ -51,7 +51,10 @@
</a-button>
</div>
<div class="toolbar-right">
<a-checkbox :checked="isSourceTrajectory" @change="handleSourceTrajectoryChange">
<a-checkbox
:model-value="isSourceTrajectory"
@change="handleSourceTrajectoryChange"
>
{{ $t('track.order.detail.showAllSourceTrajectory') }}
</a-checkbox>
</div>
@ -241,9 +244,9 @@ watch(() => props.detailData, (newData) => {
sourceTrajectoryData.value = (newData || []).map((item: any) => ({ ...item }));
}, { immediate: true, deep: true });
// 处理服务商轨迹开关变化
function handleSourceTrajectoryChange(checked: boolean) {
emit('toggleSourceTrajectory', checked);
// 处理服务商轨迹开关变化(Arco Checkbox 使用 modelValue,无 checked prop)
function handleSourceTrajectoryChange(checked: boolean | (string | number | boolean)[]) {
emit('toggleSourceTrajectory', !!checked);
}
const detailColumns = computed(() => [

View File

@ -787,6 +787,7 @@ import { useI18n } from 'vue-i18n';
// 关闭详情抽屉
function closeDetailDrawerHandler() {
currentDetailRow.value = null;
isSourceTrajectory.value = false;
}
// 使用操作处理 composable
@ -1537,13 +1538,12 @@ import { useI18n } from 'vue-i18n';
watch(
() => isSourceTrajectory.value,
(val) => {
// 勾选显示服务商全部源轨迹则把没有轨迹代码映射的服务商源轨迹展示到列表上,注意该列没有更新和删除的按钮。
if (sourceTrajectoryData.value.length === 0) return;
if (val)
sourceTrajectoryData.value =
notSourceTrajectoryDataTemporaryStorage.value;
else
sourceTrajectoryData.value = sourceTrajectoryDataTemporaryStorage.value;
// 勾选显示服务商全部源轨迹:含未映射轨迹代码的源轨迹;勿用当前列表 length===0 提前 return,
// 否则「过滤后为空、全量仍有数据」时勾选无效果。
const full = notSourceTrajectoryDataTemporaryStorage.value;
const mapped = sourceTrajectoryDataTemporaryStorage.value;
if (!full.length && !mapped.length) return;
sourceTrajectoryData.value = val ? full : mapped;
}
);