feat(@vben/web-antd): split store delivery active and config modes
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
/**
|
/**
|
||||||
* 文件职责:配送模式区块。
|
* 文件职责:配送模式区块。
|
||||||
* 1. 展示配送模式切换按钮。
|
* 1. 展示当前启用配送模式,并提供独立切换入口。
|
||||||
* 2. 展示地图占位与半径/区域两种预览。
|
* 2. 展示配置编辑视图切换与半径/区域两种预览。
|
||||||
*/
|
*/
|
||||||
import type { DeliveryMode, RadiusTierDto } from '#/api/store-delivery';
|
import type { DeliveryMode, RadiusTierDto } from '#/api/store-delivery';
|
||||||
|
|
||||||
@@ -11,7 +11,8 @@ import { computed } from 'vue';
|
|||||||
import { Card } from 'ant-design-vue';
|
import { Card } from 'ant-design-vue';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mode: DeliveryMode;
|
activeMode: DeliveryMode;
|
||||||
|
configMode: DeliveryMode;
|
||||||
modeOptions: Array<{ label: string; value: DeliveryMode }>;
|
modeOptions: Array<{ label: string; value: DeliveryMode }>;
|
||||||
radiusTiers: RadiusTierDto[];
|
radiusTiers: RadiusTierDto[];
|
||||||
}
|
}
|
||||||
@@ -19,9 +20,17 @@ interface Props {
|
|||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
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 radiusLabels = computed(() => {
|
||||||
const fallback = ['1km', '3km', '5km'];
|
const fallback = ['1km', '3km', '5km'];
|
||||||
const sorted = props.radiusTiers
|
const sorted = props.radiusTiers
|
||||||
@@ -43,14 +52,36 @@ const radiusLabels = computed(() => {
|
|||||||
<span class="section-title">配送模式</span>
|
<span class="section-title">配送模式</span>
|
||||||
</template>
|
</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">
|
<div class="delivery-mode-switch">
|
||||||
<button
|
<button
|
||||||
v-for="item in props.modeOptions"
|
v-for="item in props.modeOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
type="button"
|
type="button"
|
||||||
class="mode-switch-item"
|
class="mode-switch-item"
|
||||||
:class="{ active: props.mode === item.value }"
|
:class="{ active: props.configMode === item.value }"
|
||||||
@click="emit('changeMode', item.value)"
|
@click="emit('changeConfigMode', item.value)"
|
||||||
>
|
>
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</button>
|
</button>
|
||||||
@@ -66,7 +97,7 @@ const radiusLabels = computed(() => {
|
|||||||
|
|
||||||
<span class="map-pin">●</span>
|
<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-circle radius-3">
|
||||||
<span class="radius-label">{{ radiusLabels[2] }}</span>
|
<span class="radius-label">{{ radiusLabels[2] }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
} from './helpers';
|
} from './helpers';
|
||||||
|
|
||||||
interface CreateDataActionsOptions {
|
interface CreateDataActionsOptions {
|
||||||
|
editingMode: Ref<DeliveryMode>;
|
||||||
generalSettings: DeliveryGeneralSettingsDto;
|
generalSettings: DeliveryGeneralSettingsDto;
|
||||||
isSaving: Ref<boolean>;
|
isSaving: Ref<boolean>;
|
||||||
isSettingsLoading: Ref<boolean>;
|
isSettingsLoading: Ref<boolean>;
|
||||||
@@ -62,6 +63,7 @@ export function createDataActions(options: CreateDataActionsOptions) {
|
|||||||
/** 将快照应用到页面状态。 */
|
/** 将快照应用到页面状态。 */
|
||||||
function applySnapshot(snapshot: DeliverySettingsSnapshot) {
|
function applySnapshot(snapshot: DeliverySettingsSnapshot) {
|
||||||
options.mode.value = snapshot.mode;
|
options.mode.value = snapshot.mode;
|
||||||
|
options.editingMode.value = snapshot.mode;
|
||||||
options.radiusTiers.value = sortRadiusTiers(snapshot.radiusTiers);
|
options.radiusTiers.value = sortRadiusTiers(snapshot.radiusTiers);
|
||||||
options.polygonZones.value = sortPolygonZones(snapshot.polygonZones);
|
options.polygonZones.value = sortPolygonZones(snapshot.polygonZones);
|
||||||
syncGeneralSettings(snapshot.generalSettings);
|
syncGeneralSettings(snapshot.generalSettings);
|
||||||
@@ -80,6 +82,7 @@ export function createDataActions(options: CreateDataActionsOptions) {
|
|||||||
/** 回填默认配置,作为接口异常时的兜底展示。 */
|
/** 回填默认配置,作为接口异常时的兜底展示。 */
|
||||||
function applyDefaultSettings() {
|
function applyDefaultSettings() {
|
||||||
options.mode.value = DEFAULT_DELIVERY_MODE;
|
options.mode.value = DEFAULT_DELIVERY_MODE;
|
||||||
|
options.editingMode.value = DEFAULT_DELIVERY_MODE;
|
||||||
options.radiusTiers.value = sortRadiusTiers(DEFAULT_RADIUS_TIERS);
|
options.radiusTiers.value = sortRadiusTiers(DEFAULT_RADIUS_TIERS);
|
||||||
options.polygonZones.value = sortPolygonZones(DEFAULT_POLYGON_ZONES);
|
options.polygonZones.value = sortPolygonZones(DEFAULT_POLYGON_ZONES);
|
||||||
syncGeneralSettings(cloneGeneralSettings(DEFAULT_GENERAL_SETTINGS));
|
syncGeneralSettings(cloneGeneralSettings(DEFAULT_GENERAL_SETTINGS));
|
||||||
@@ -94,6 +97,7 @@ export function createDataActions(options: CreateDataActionsOptions) {
|
|||||||
if (options.selectedStoreId.value !== currentStoreId) return;
|
if (options.selectedStoreId.value !== currentStoreId) return;
|
||||||
|
|
||||||
options.mode.value = result.mode ?? DEFAULT_DELIVERY_MODE;
|
options.mode.value = result.mode ?? DEFAULT_DELIVERY_MODE;
|
||||||
|
options.editingMode.value = options.mode.value;
|
||||||
options.radiusTiers.value = sortRadiusTiers(
|
options.radiusTiers.value = sortRadiusTiers(
|
||||||
result.radiusTiers?.length ? result.radiusTiers : DEFAULT_RADIUS_TIERS,
|
result.radiusTiers?.length ? result.radiusTiers : DEFAULT_RADIUS_TIERS,
|
||||||
);
|
);
|
||||||
@@ -166,7 +170,7 @@ export function createDataActions(options: CreateDataActionsOptions) {
|
|||||||
|
|
||||||
/** 保存当前门店配送设置。 */
|
/** 保存当前门店配送设置。 */
|
||||||
async function saveCurrentSettings() {
|
async function saveCurrentSettings() {
|
||||||
if (!options.selectedStoreId.value) return;
|
if (!options.selectedStoreId.value) return false;
|
||||||
options.isSaving.value = true;
|
options.isSaving.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -179,8 +183,11 @@ export function createDataActions(options: CreateDataActionsOptions) {
|
|||||||
});
|
});
|
||||||
options.snapshot.value = buildCurrentSnapshot();
|
options.snapshot.value = buildCurrentSnapshot();
|
||||||
message.success('配送设置已保存');
|
message.success('配送设置已保存');
|
||||||
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
message.error('配送设置保存失败,请稍后重试');
|
||||||
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
options.isSaving.value = false;
|
options.isSaving.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ import type {
|
|||||||
RadiusTierDto,
|
RadiusTierDto,
|
||||||
} from '#/api/store-delivery';
|
} 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 {
|
import {
|
||||||
DEFAULT_DELIVERY_MODE,
|
DEFAULT_DELIVERY_MODE,
|
||||||
@@ -56,7 +58,10 @@ export function useStoreDeliveryPage() {
|
|||||||
// 2. 页面主业务数据。
|
// 2. 页面主业务数据。
|
||||||
const stores = ref<StoreListItemDto[]>([]);
|
const stores = ref<StoreListItemDto[]>([]);
|
||||||
const selectedStoreId = ref('');
|
const selectedStoreId = ref('');
|
||||||
|
// 当前生效配送模式(会落库)。
|
||||||
const deliveryMode = ref<DeliveryMode>(DEFAULT_DELIVERY_MODE);
|
const deliveryMode = ref<DeliveryMode>(DEFAULT_DELIVERY_MODE);
|
||||||
|
// 当前编辑视图模式(仅影响页面展示,不直接落库)。
|
||||||
|
const editingMode = ref<DeliveryMode>(DEFAULT_DELIVERY_MODE);
|
||||||
const radiusTiers = ref<RadiusTierDto[]>(
|
const radiusTiers = ref<RadiusTierDto[]>(
|
||||||
cloneRadiusTiers(DEFAULT_RADIUS_TIERS),
|
cloneRadiusTiers(DEFAULT_RADIUS_TIERS),
|
||||||
);
|
);
|
||||||
@@ -123,7 +128,7 @@ export function useStoreDeliveryPage() {
|
|||||||
copyTargetStoreIds.value.length < copyCandidates.value.length,
|
copyTargetStoreIds.value.length < copyCandidates.value.length,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isRadiusMode = computed(() => deliveryMode.value === 'radius');
|
const isRadiusMode = computed(() => editingMode.value === 'radius');
|
||||||
const isPageLoading = computed(() => isSettingsLoading.value);
|
const isPageLoading = computed(() => isSettingsLoading.value);
|
||||||
|
|
||||||
const tierDrawerTitle = computed(() =>
|
const tierDrawerTitle = computed(() =>
|
||||||
@@ -140,6 +145,7 @@ export function useStoreDeliveryPage() {
|
|||||||
resetFromSnapshot,
|
resetFromSnapshot,
|
||||||
saveCurrentSettings,
|
saveCurrentSettings,
|
||||||
} = createDataActions({
|
} = createDataActions({
|
||||||
|
editingMode,
|
||||||
generalSettings,
|
generalSettings,
|
||||||
isSaving,
|
isSaving,
|
||||||
isSettingsLoading,
|
isSettingsLoading,
|
||||||
@@ -212,8 +218,34 @@ export function useStoreDeliveryPage() {
|
|||||||
selectedStoreId.value = value;
|
selectedStoreId.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换“配置编辑视图”,不直接修改生效模式。
|
||||||
|
function setEditingMode(value: DeliveryMode) {
|
||||||
|
editingMode.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换“当前生效模式”,二次确认后保存,防止误操作。
|
||||||
function setDeliveryMode(value: DeliveryMode) {
|
function setDeliveryMode(value: DeliveryMode) {
|
||||||
|
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;
|
deliveryMode.value = value;
|
||||||
|
editingMode.value = value;
|
||||||
|
const saved = await saveCurrentSettings();
|
||||||
|
if (!saved) {
|
||||||
|
deliveryMode.value = previousMode;
|
||||||
|
editingMode.value = previousEditingMode;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFreeDeliveryThreshold(value: null | number) {
|
function setFreeDeliveryThreshold(value: null | number) {
|
||||||
@@ -246,6 +278,7 @@ export function useStoreDeliveryPage() {
|
|||||||
watch(selectedStoreId, async (storeId) => {
|
watch(selectedStoreId, async (storeId) => {
|
||||||
if (!storeId) {
|
if (!storeId) {
|
||||||
deliveryMode.value = DEFAULT_DELIVERY_MODE;
|
deliveryMode.value = DEFAULT_DELIVERY_MODE;
|
||||||
|
editingMode.value = DEFAULT_DELIVERY_MODE;
|
||||||
radiusTiers.value = cloneRadiusTiers(DEFAULT_RADIUS_TIERS);
|
radiusTiers.value = cloneRadiusTiers(DEFAULT_RADIUS_TIERS);
|
||||||
polygonZones.value = clonePolygonZones(DEFAULT_POLYGON_ZONES);
|
polygonZones.value = clonePolygonZones(DEFAULT_POLYGON_ZONES);
|
||||||
Object.assign(
|
Object.assign(
|
||||||
@@ -260,6 +293,8 @@ export function useStoreDeliveryPage() {
|
|||||||
|
|
||||||
// 8. 页面首屏初始化。
|
// 8. 页面首屏初始化。
|
||||||
onMounted(loadStores);
|
onMounted(loadStores);
|
||||||
|
// 9. 路由回到当前页时刷新门店列表,避免使用旧缓存。
|
||||||
|
onActivated(loadStores);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
DELIVERY_MODE_OPTIONS,
|
DELIVERY_MODE_OPTIONS,
|
||||||
@@ -295,6 +330,8 @@ export function useStoreDeliveryPage() {
|
|||||||
selectedStoreId,
|
selectedStoreId,
|
||||||
selectedStoreName,
|
selectedStoreName,
|
||||||
setDeliveryMode,
|
setDeliveryMode,
|
||||||
|
editingMode,
|
||||||
|
setEditingMode,
|
||||||
setEtaAdjustmentMinutes,
|
setEtaAdjustmentMinutes,
|
||||||
setFreeDeliveryThreshold,
|
setFreeDeliveryThreshold,
|
||||||
setHourlyCapacityLimit,
|
setHourlyCapacityLimit,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const {
|
|||||||
copyCandidates,
|
copyCandidates,
|
||||||
copyTargetStoreIds,
|
copyTargetStoreIds,
|
||||||
deliveryMode,
|
deliveryMode,
|
||||||
|
editingMode,
|
||||||
formatCurrency,
|
formatCurrency,
|
||||||
formatDistanceRange,
|
formatDistanceRange,
|
||||||
generalSettings,
|
generalSettings,
|
||||||
@@ -52,6 +53,7 @@ const {
|
|||||||
selectedStoreId,
|
selectedStoreId,
|
||||||
selectedStoreName,
|
selectedStoreName,
|
||||||
setDeliveryMode,
|
setDeliveryMode,
|
||||||
|
setEditingMode,
|
||||||
setEtaAdjustmentMinutes,
|
setEtaAdjustmentMinutes,
|
||||||
setFreeDeliveryThreshold,
|
setFreeDeliveryThreshold,
|
||||||
setHourlyCapacityLimit,
|
setHourlyCapacityLimit,
|
||||||
@@ -101,10 +103,12 @@ const {
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<Spin :spinning="isPageLoading">
|
<Spin :spinning="isPageLoading">
|
||||||
<DeliveryModeCard
|
<DeliveryModeCard
|
||||||
:mode="deliveryMode"
|
:active-mode="deliveryMode"
|
||||||
|
:config-mode="editingMode"
|
||||||
:mode-options="DELIVERY_MODE_OPTIONS"
|
:mode-options="DELIVERY_MODE_OPTIONS"
|
||||||
:radius-tiers="radiusTiers"
|
:radius-tiers="radiusTiers"
|
||||||
@change-mode="setDeliveryMode"
|
@change-active-mode="setDeliveryMode"
|
||||||
|
@change-config-mode="setEditingMode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RadiusTierSection
|
<RadiusTierSection
|
||||||
|
|||||||
@@ -1,5 +1,47 @@
|
|||||||
/* 文件职责:配送模式切换与地图占位样式。 */
|
/* 文件职责:配送模式切换与地图占位样式。 */
|
||||||
.page-store-delivery {
|
.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 {
|
.delivery-mode-switch {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
|
|||||||
Reference in New Issue
Block a user