feat: 批量工具面板改为抽屉式交互
All checks were successful
Build and Deploy TenantUI / build-and-deploy (push) Successful in 51s

This commit is contained in:
2026-02-26 13:50:52 +08:00
parent 97e833d8d8
commit 2617234d9e
8 changed files with 210 additions and 109 deletions

View File

@@ -4,6 +4,7 @@ import { Button, Select, Space, Upload } from 'ant-design-vue';
interface Props {
categoryOptions: Array<{ label: string; value: string }>;
embedded?: boolean;
exportCategoryIds: string[];
exportScopeType: 'all' | 'category';
open: boolean;
@@ -21,7 +22,9 @@ interface Emits {
(event: 'update:exportScopeType', value: 'all' | 'category'): void;
}
const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
embedded: false,
});
const emit = defineEmits<Emits>();
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
@@ -40,8 +43,12 @@ function setExportCategoryIds(value: unknown) {
</script>
<template>
<section v-if="props.open" class="pbt-panel">
<header class="pbt-panel-hd">
<section
v-if="props.open"
class="pbt-panel"
:class="{ 'pbt-panel-embedded': props.embedded }"
>
<header v-if="!props.embedded" class="pbt-panel-hd">
<h3>导入导出</h3>
<button type="button" class="pbt-close" @click="emit('close')">×</button>
</header>

View File

@@ -3,6 +3,7 @@ import { Button, Select } from 'ant-design-vue';
interface Props {
categoryOptions: Array<{ label: string; value: string }>;
embedded?: boolean;
estimatedCount: number;
open: boolean;
sourceCategoryId: string;
@@ -17,7 +18,9 @@ interface Emits {
(event: 'update:targetCategoryId', value: string): void;
}
const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
embedded: false,
});
const emit = defineEmits<Emits>();
function setSourceCategory(value: unknown) {
@@ -30,8 +33,12 @@ function setTargetCategory(value: unknown) {
</script>
<template>
<section v-if="props.open" class="pbt-panel">
<header class="pbt-panel-hd">
<section
v-if="props.open"
class="pbt-panel"
:class="{ 'pbt-panel-embedded': props.embedded }"
>
<header v-if="!props.embedded" class="pbt-panel-hd">
<h3>批量移动分类</h3>
<button type="button" class="pbt-close" @click="emit('close')">×</button>
</header>

View File

@@ -9,6 +9,7 @@ interface Props {
amountType: 'fixed' | 'percent';
categoryOptions: Array<{ label: string; value: string }>;
direction: 'down' | 'up';
embedded?: boolean;
open: boolean;
previewItems: BatchPricePreviewRow[];
previewLoading: boolean;
@@ -32,7 +33,9 @@ interface Emits {
(event: 'update:scopeType', value: BatchScopeType): void;
}
const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
embedded: false,
});
const emit = defineEmits<Emits>();
const scopePills = [
@@ -93,8 +96,12 @@ function setAmount(value: null | number | string) {
</script>
<template>
<section v-if="props.open" class="pbt-panel">
<header class="pbt-panel-hd">
<section
v-if="props.open"
class="pbt-panel"
:class="{ 'pbt-panel-embedded': props.embedded }"
>
<header v-if="!props.embedded" class="pbt-panel-hd">
<h3>批量调价</h3>
<button type="button" class="pbt-close" @click="emit('close')">×</button>
</header>

View File

@@ -5,6 +5,7 @@ import { Button, Select } from 'ant-design-vue';
interface Props {
action: 'off' | 'on';
categoryOptions: Array<{ label: string; value: string }>;
embedded?: boolean;
estimatedCount: number;
open: boolean;
productOptions: Array<{ label: string; value: string }>;
@@ -23,7 +24,9 @@ interface Emits {
(event: 'update:scopeType', value: BatchScopeType): void;
}
const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
embedded: false,
});
const emit = defineEmits<Emits>();
const scopePills = [
@@ -54,8 +57,12 @@ function setScopeProductIds(value: unknown) {
</script>
<template>
<section v-if="props.open" class="pbt-panel">
<header class="pbt-panel-hd">
<section
v-if="props.open"
class="pbt-panel"
:class="{ 'pbt-panel-embedded': props.embedded }"
>
<header v-if="!props.embedded" class="pbt-panel-hd">
<h3>批量上下架</h3>
<button type="button" class="pbt-close" @click="emit('close')">×</button>
</header>

View File

@@ -2,6 +2,7 @@
import { Button, Checkbox, Input, Select, Space } from 'ant-design-vue';
interface Props {
embedded?: boolean;
open: boolean;
productIds: string[];
productOptions: Array<{ label: string; value: string }>;
@@ -24,7 +25,9 @@ interface Emits {
(event: 'update:targetStoreIds', value: string[]): void;
}
const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
embedded: false,
});
const emit = defineEmits<Emits>();
function normalizeArray(value: unknown) {
@@ -56,8 +59,12 @@ function setSyncStatus(value: boolean | string | undefined) {
</script>
<template>
<section v-if="props.open" class="pbt-panel">
<header class="pbt-panel-hd">
<section
v-if="props.open"
class="pbt-panel"
:class="{ 'pbt-panel-embedded': props.embedded }"
>
<header v-if="!props.embedded" class="pbt-panel-hd">
<h3>批量同步门店</h3>
<button type="button" class="pbt-close" @click="emit('close')">×</button>
</header>

View File

@@ -1,7 +1,8 @@
<script setup lang="ts">
import { Page } from '@vben/common-ui';
import { computed } from 'vue';
import { Alert, Empty, Spin } from 'ant-design-vue';
import { Alert, Drawer, Empty, Spin } from 'ant-design-vue';
import BatchImportExportPanel from './components/BatchImportExportPanel.vue';
import BatchMoveCategoryPanel from './components/BatchMoveCategoryPanel.vue';
@@ -11,6 +12,7 @@ import BatchStoreSyncPanel from './components/BatchStoreSyncPanel.vue';
import StoreScopeToolbar from '../../shared/components/StoreScopeToolbar.vue';
import BatchToolCards from './components/BatchToolCards.vue';
import { useProductBatchPage } from './composables/useProductBatchPage';
import type { BatchToolKey } from './types';
const {
activeTool,
@@ -119,6 +121,21 @@ function updateExportScopeType(value: 'all' | 'category') {
function updateExportCategoryIds(value: string[]) {
exportCategoryIds.value = value;
}
const toolTitleMap: Record<BatchToolKey, string> = {
price: '批量调价',
sale: '批量上下架',
category: '批量移动分类',
sync: '批量同步门店',
excel: '导入导出',
};
const activeToolTitle = computed(() => {
if (!activeTool.value) {
return '';
}
return toolTitleMap[activeTool.value];
});
</script>
<template>
@@ -151,9 +168,28 @@ function updateExportCategoryIds(value: string[]) {
:active-tool="activeTool"
@toggle="toggleTool"
/>
</template>
<Alert
v-if="latestResultText"
class="pbt-result"
type="success"
show-icon
:message="latestResultText"
/>
</div>
<Drawer
class="pbt-drawer"
:title="activeToolTitle"
:open="Boolean(activeTool)"
:width="920"
@close="closeTool"
>
<BatchPriceAdjustPanel
:open="activeTool === 'price'"
v-if="activeTool === 'price'"
embedded
:open="true"
:scope-type="scopeType"
:scope-category-ids="scopeCategoryIds"
:scope-product-ids="scopeProductIds"
@@ -178,7 +214,9 @@ function updateExportCategoryIds(value: string[]) {
/>
<BatchSaleSwitchPanel
:open="activeTool === 'sale'"
v-if="activeTool === 'sale'"
embedded
:open="true"
:scope-type="scopeType"
:scope-category-ids="scopeCategoryIds"
:scope-product-ids="scopeProductIds"
@@ -196,7 +234,9 @@ function updateExportCategoryIds(value: string[]) {
/>
<BatchMoveCategoryPanel
:open="activeTool === 'category'"
v-if="activeTool === 'category'"
embedded
:open="true"
:category-options="categoryOptions"
:source-category-id="moveSourceCategoryId"
:target-category-id="moveTargetCategoryId"
@@ -209,7 +249,9 @@ function updateExportCategoryIds(value: string[]) {
/>
<BatchStoreSyncPanel
:open="activeTool === 'sync'"
v-if="activeTool === 'sync'"
embedded
:open="true"
:source-store-name="selectedStoreLabel"
:target-store-ids="syncTargetStoreIds"
:target-store-options="targetStoreOptions"
@@ -229,7 +271,9 @@ function updateExportCategoryIds(value: string[]) {
/>
<BatchImportExportPanel
:open="activeTool === 'excel'"
v-if="activeTool === 'excel'"
embedded
:open="true"
:selected-file-name="importFile?.name || ''"
:export-scope-type="exportScopeType"
:export-category-ids="exportCategoryIds"
@@ -243,16 +287,7 @@ function updateExportCategoryIds(value: string[]) {
@submit-export="submitExport"
@close="closeTool"
/>
</template>
<Alert
v-if="latestResultText"
class="pbt-result"
type="success"
show-icon
:message="latestResultText"
/>
</div>
</Drawer>
</Page>
</template>

View File

@@ -1,4 +1,22 @@
.page-product-batch {
.pbt-drawer .ant-drawer-header {
padding: 14px 20px;
border-bottom: 1px solid #f0f0f0;
}
.pbt-drawer .ant-drawer-title {
font-size: 15px;
font-weight: 600;
color: #1a1a2e;
}
.pbt-drawer .ant-drawer-body {
padding: 16px 20px 20px;
background: #f8fafc;
}
.page-product-batch,
.pbt-drawer {
.pbt-panel {
padding: 20px;
background: #fff;
@@ -6,6 +24,13 @@
box-shadow: var(--pbt-shadow-sm);
}
.pbt-panel.pbt-panel-embedded {
padding: 0;
background: transparent;
border-radius: 0;
box-shadow: none;
}
.pbt-panel-hd {
display: flex;
align-items: center;

View File

@@ -21,3 +21,9 @@
}
}
}
@media (width <= 1200px) {
.pbt-drawer .ant-drawer-content-wrapper {
width: min(920px, 100vw) !important;
}
}