feat(@vben/web-antd): split store delivery active and config modes
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user