feat(@vben/web-antd): split store delivery active and config modes

This commit is contained in:
2026-02-19 08:02:49 +08:00
parent f7854bb925
commit b4a63cb0b8
5 changed files with 134 additions and 13 deletions

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
/**
* 文件职责:配送模式区块。
* 1. 展示配送模式切换按钮
* 2. 展示地图占位与半径/区域两种预览。
* 1. 展示当前启用配送模式,并提供独立切换入口
* 2. 展示配置编辑视图切换与半径/区域两种预览。
*/
import type { DeliveryMode, RadiusTierDto } from '#/api/store-delivery';
@@ -11,7 +11,8 @@ import { computed } from 'vue';
import { Card } from 'ant-design-vue';
interface Props {
mode: DeliveryMode;
activeMode: DeliveryMode;
configMode: DeliveryMode;
modeOptions: Array<{ label: string; value: DeliveryMode }>;
radiusTiers: RadiusTierDto[];
}
@@ -19,9 +20,17 @@ interface Props {
const props = defineProps<Props>();
const emit = defineEmits<{
(event: 'changeMode', mode: DeliveryMode): void;
(event: 'changeActiveMode', mode: DeliveryMode): void;
(event: 'changeConfigMode', mode: DeliveryMode): void;
}>();
const activeModeLabel = computed(() => {
return (
props.modeOptions.find((item) => item.value === props.activeMode)?.label ??
'--'
);
});
const radiusLabels = computed(() => {
const fallback = ['1km', '3km', '5km'];
const sorted = props.radiusTiers
@@ -43,14 +52,36 @@ const radiusLabels = computed(() => {
<span class="section-title">配送模式</span>
</template>
<div class="delivery-active-mode">
<div class="delivery-active-title">当前已启用模式</div>
<div class="delivery-active-value">{{ activeModeLabel }}</div>
<div class="delivery-active-tip">
切换后将影响顾客下单时的配送范围与配送费计算
</div>
<div class="delivery-active-switch">
<button
v-for="item in props.modeOptions"
:key="item.value"
type="button"
class="mode-switch-item"
:class="{ active: props.activeMode === item.value }"
@click="emit('changeActiveMode', item.value)"
>
{{ item.label }}
</button>
</div>
</div>
<div class="delivery-config-title">配送配置编辑视图</div>
<div class="delivery-mode-switch">
<button
v-for="item in props.modeOptions"
:key="item.value"
type="button"
class="mode-switch-item"
:class="{ active: props.mode === item.value }"
@click="emit('changeMode', item.value)"
:class="{ active: props.configMode === item.value }"
@click="emit('changeConfigMode', item.value)"
>
{{ item.label }}
</button>
@@ -66,7 +97,7 @@ const radiusLabels = computed(() => {
<span class="map-pin"></span>
<template v-if="props.mode === 'radius'">
<template v-if="props.configMode === 'radius'">
<span class="radius-circle radius-3">
<span class="radius-label">{{ radiusLabels[2] }}</span>
</span>

View File

@@ -38,6 +38,7 @@ import {
} from './helpers';
interface CreateDataActionsOptions {
editingMode: Ref<DeliveryMode>;
generalSettings: DeliveryGeneralSettingsDto;
isSaving: Ref<boolean>;
isSettingsLoading: Ref<boolean>;
@@ -62,6 +63,7 @@ export function createDataActions(options: CreateDataActionsOptions) {
/** 将快照应用到页面状态。 */
function applySnapshot(snapshot: DeliverySettingsSnapshot) {
options.mode.value = snapshot.mode;
options.editingMode.value = snapshot.mode;
options.radiusTiers.value = sortRadiusTiers(snapshot.radiusTiers);
options.polygonZones.value = sortPolygonZones(snapshot.polygonZones);
syncGeneralSettings(snapshot.generalSettings);
@@ -80,6 +82,7 @@ export function createDataActions(options: CreateDataActionsOptions) {
/** 回填默认配置,作为接口异常时的兜底展示。 */
function applyDefaultSettings() {
options.mode.value = DEFAULT_DELIVERY_MODE;
options.editingMode.value = DEFAULT_DELIVERY_MODE;
options.radiusTiers.value = sortRadiusTiers(DEFAULT_RADIUS_TIERS);
options.polygonZones.value = sortPolygonZones(DEFAULT_POLYGON_ZONES);
syncGeneralSettings(cloneGeneralSettings(DEFAULT_GENERAL_SETTINGS));
@@ -94,6 +97,7 @@ export function createDataActions(options: CreateDataActionsOptions) {
if (options.selectedStoreId.value !== currentStoreId) return;
options.mode.value = result.mode ?? DEFAULT_DELIVERY_MODE;
options.editingMode.value = options.mode.value;
options.radiusTiers.value = sortRadiusTiers(
result.radiusTiers?.length ? result.radiusTiers : DEFAULT_RADIUS_TIERS,
);
@@ -166,7 +170,7 @@ export function createDataActions(options: CreateDataActionsOptions) {
/** 保存当前门店配送设置。 */
async function saveCurrentSettings() {
if (!options.selectedStoreId.value) return;
if (!options.selectedStoreId.value) return false;
options.isSaving.value = true;
try {
@@ -179,8 +183,11 @@ export function createDataActions(options: CreateDataActionsOptions) {
});
options.snapshot.value = buildCurrentSnapshot();
message.success('配送设置已保存');
return true;
} catch (error) {
console.error(error);
message.error('配送设置保存失败,请稍后重试');
return false;
} finally {
options.isSaving.value = false;
}

View File

@@ -19,7 +19,9 @@ import type {
RadiusTierDto,
} from '#/api/store-delivery';
import { computed, onMounted, reactive, ref, watch } from 'vue';
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue';
import { Modal } from 'ant-design-vue';
import {
DEFAULT_DELIVERY_MODE,
@@ -56,7 +58,10 @@ export function useStoreDeliveryPage() {
// 2. 页面主业务数据。
const stores = ref<StoreListItemDto[]>([]);
const selectedStoreId = ref('');
// 当前生效配送模式(会落库)。
const deliveryMode = ref<DeliveryMode>(DEFAULT_DELIVERY_MODE);
// 当前编辑视图模式(仅影响页面展示,不直接落库)。
const editingMode = ref<DeliveryMode>(DEFAULT_DELIVERY_MODE);
const radiusTiers = ref<RadiusTierDto[]>(
cloneRadiusTiers(DEFAULT_RADIUS_TIERS),
);
@@ -123,7 +128,7 @@ export function useStoreDeliveryPage() {
copyTargetStoreIds.value.length < copyCandidates.value.length,
);
const isRadiusMode = computed(() => deliveryMode.value === 'radius');
const isRadiusMode = computed(() => editingMode.value === 'radius');
const isPageLoading = computed(() => isSettingsLoading.value);
const tierDrawerTitle = computed(() =>
@@ -140,6 +145,7 @@ export function useStoreDeliveryPage() {
resetFromSnapshot,
saveCurrentSettings,
} = createDataActions({
editingMode,
generalSettings,
isSaving,
isSettingsLoading,
@@ -212,8 +218,34 @@ export function useStoreDeliveryPage() {
selectedStoreId.value = value;
}
// 切换“配置编辑视图”,不直接修改生效模式。
function setEditingMode(value: DeliveryMode) {
editingMode.value = value;
}
// 切换“当前生效模式”,二次确认后保存,防止误操作。
function setDeliveryMode(value: DeliveryMode) {
deliveryMode.value = value;
if (value === deliveryMode.value) return;
Modal.confirm({
title: '确认切换当前启用的配送模式?',
content:
value === 'radius'
? '切换后将使用按半径配送规则进行计费与可达性判断。'
: '切换后将使用按区域配送(多边形)规则进行计费与可达性判断。',
okText: '确认切换',
cancelText: '取消',
async onOk() {
const previousMode = deliveryMode.value;
const previousEditingMode = editingMode.value;
deliveryMode.value = value;
editingMode.value = value;
const saved = await saveCurrentSettings();
if (!saved) {
deliveryMode.value = previousMode;
editingMode.value = previousEditingMode;
}
},
});
}
function setFreeDeliveryThreshold(value: null | number) {
@@ -246,6 +278,7 @@ export function useStoreDeliveryPage() {
watch(selectedStoreId, async (storeId) => {
if (!storeId) {
deliveryMode.value = DEFAULT_DELIVERY_MODE;
editingMode.value = DEFAULT_DELIVERY_MODE;
radiusTiers.value = cloneRadiusTiers(DEFAULT_RADIUS_TIERS);
polygonZones.value = clonePolygonZones(DEFAULT_POLYGON_ZONES);
Object.assign(
@@ -260,6 +293,8 @@ export function useStoreDeliveryPage() {
// 8. 页面首屏初始化。
onMounted(loadStores);
// 9. 路由回到当前页时刷新门店列表,避免使用旧缓存。
onActivated(loadStores);
return {
DELIVERY_MODE_OPTIONS,
@@ -295,6 +330,8 @@ export function useStoreDeliveryPage() {
selectedStoreId,
selectedStoreName,
setDeliveryMode,
editingMode,
setEditingMode,
setEtaAdjustmentMinutes,
setFreeDeliveryThreshold,
setHourlyCapacityLimit,

View File

@@ -23,6 +23,7 @@ const {
copyCandidates,
copyTargetStoreIds,
deliveryMode,
editingMode,
formatCurrency,
formatDistanceRange,
generalSettings,
@@ -52,6 +53,7 @@ const {
selectedStoreId,
selectedStoreName,
setDeliveryMode,
setEditingMode,
setEtaAdjustmentMinutes,
setFreeDeliveryThreshold,
setHourlyCapacityLimit,
@@ -101,10 +103,12 @@ const {
<template v-else>
<Spin :spinning="isPageLoading">
<DeliveryModeCard
:mode="deliveryMode"
:active-mode="deliveryMode"
:config-mode="editingMode"
:mode-options="DELIVERY_MODE_OPTIONS"
:radius-tiers="radiusTiers"
@change-mode="setDeliveryMode"
@change-active-mode="setDeliveryMode"
@change-config-mode="setEditingMode"
/>
<RadiusTierSection

View File

@@ -1,5 +1,47 @@
/* 文件职责:配送模式切换与地图占位样式。 */
.page-store-delivery {
.delivery-active-mode {
padding: 14px 16px;
margin-bottom: 16px;
background: #f8fbff;
border: 1px solid #d6e4ff;
border-radius: 10px;
}
.delivery-active-title {
margin-bottom: 4px;
font-size: 13px;
color: #475569;
}
.delivery-active-value {
margin-bottom: 6px;
font-size: 16px;
font-weight: 600;
color: #1d4ed8;
}
.delivery-active-tip {
margin-bottom: 10px;
font-size: 12px;
color: #64748b;
}
.delivery-active-switch {
display: flex;
gap: 2px;
width: fit-content;
padding: 3px;
background: #f1f5ff;
border-radius: 8px;
}
.delivery-config-title {
margin-bottom: 8px;
font-size: 13px;
color: #475569;
}
.delivery-mode-switch {
display: flex;
gap: 2px;