feat(project): pickup mode switch with auto-save and rollback
This commit is contained in:
@@ -101,6 +101,12 @@ export interface SavePickupFineRuleParams {
|
||||
storeId: string;
|
||||
}
|
||||
|
||||
/** 保存自提模式参数 */
|
||||
export interface SavePickupModeParams {
|
||||
mode: PickupMode;
|
||||
storeId: string;
|
||||
}
|
||||
|
||||
/** 复制自提设置参数 */
|
||||
export interface CopyStorePickupSettingsParams {
|
||||
sourceStoreId: string;
|
||||
@@ -131,6 +137,11 @@ export async function savePickupFineRuleApi(data: SavePickupFineRuleParams) {
|
||||
return requestClient.post('/store/pickup/fine-rule/save', data);
|
||||
}
|
||||
|
||||
/** 保存自提模式 */
|
||||
export async function savePickupModeApi(data: SavePickupModeParams) {
|
||||
return requestClient.post('/store/pickup/mode/save', data);
|
||||
}
|
||||
|
||||
/** 复制到其他门店 */
|
||||
export async function copyStorePickupSettingsApi(
|
||||
data: CopyStorePickupSettingsParams,
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
*/
|
||||
import type { PickupMode } from '#/api/store-pickup';
|
||||
|
||||
import { Switch } from 'ant-design-vue';
|
||||
|
||||
interface Props {
|
||||
disabled?: boolean;
|
||||
isSwitching?: boolean;
|
||||
mode: PickupMode;
|
||||
options: Array<{ label: string; value: PickupMode }>;
|
||||
}
|
||||
@@ -16,19 +20,58 @@ const props = defineProps<Props>();
|
||||
const emit = defineEmits<{
|
||||
(event: 'change', mode: PickupMode): void;
|
||||
}>();
|
||||
|
||||
function getModeLabel(mode: PickupMode) {
|
||||
return props.options.find((item) => item.value === mode)?.label ?? mode;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pickup-mode-switch">
|
||||
<button
|
||||
v-for="item in props.options"
|
||||
:key="item.value"
|
||||
type="button"
|
||||
class="pickup-mode-item"
|
||||
:class="{ active: props.mode === item.value }"
|
||||
@click="emit('change', item.value)"
|
||||
<div class="pickup-mode-switch-wrap">
|
||||
<div class="pickup-mode-switch">
|
||||
<button
|
||||
type="button"
|
||||
class="pickup-mode-item"
|
||||
:class="{ active: props.mode === 'big' }"
|
||||
:disabled="props.disabled || props.isSwitching"
|
||||
@click="emit('change', 'big')"
|
||||
>
|
||||
{{ getModeLabel('big') }}
|
||||
</button>
|
||||
|
||||
<Switch
|
||||
:checked="props.mode === 'fine'"
|
||||
:loading="props.isSwitching"
|
||||
:disabled="props.disabled || props.isSwitching"
|
||||
@update:checked="(checked) => emit('change', checked ? 'fine' : 'big')"
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="pickup-mode-item"
|
||||
:class="{ active: props.mode === 'fine' }"
|
||||
:disabled="props.disabled || props.isSwitching"
|
||||
@click="emit('change', 'fine')"
|
||||
>
|
||||
{{ getModeLabel('fine') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="pickup-mode-guide"
|
||||
:class="
|
||||
props.mode === 'fine'
|
||||
? 'pickup-mode-guide-fine'
|
||||
: 'pickup-mode-guide-big'
|
||||
"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
<div class="guide-title">当前生效规则</div>
|
||||
<div v-if="props.mode === 'big'" class="guide-desc">
|
||||
顾客按大时段进行预约,系统按已配置时段控制可约时间与容量。
|
||||
</div>
|
||||
<div v-else class="guide-desc">
|
||||
顾客按精细时间窗口预约,系统按精细规则自动生成可约时间点。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -18,6 +18,10 @@ import type {
|
||||
|
||||
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { savePickupModeApi } from '#/api/store-pickup';
|
||||
|
||||
import {
|
||||
ALL_WEEK_DAYS,
|
||||
DEFAULT_FINE_RULE,
|
||||
@@ -35,6 +39,7 @@ import {
|
||||
cloneBasicSettings,
|
||||
cloneFineRule,
|
||||
clonePreviewDays,
|
||||
createSettingsSnapshot,
|
||||
createSlotId,
|
||||
formatDayOfWeeksText,
|
||||
} from './pickup-page/helpers';
|
||||
@@ -47,6 +52,7 @@ export function useStorePickupPage() {
|
||||
const isSavingBasic = ref(false);
|
||||
const isSavingSlots = ref(false);
|
||||
const isSavingFineRule = ref(false);
|
||||
const isModeSwitching = ref(false);
|
||||
const isCopySubmitting = ref(false);
|
||||
const isConfigured = ref(false);
|
||||
const loadedStoreId = ref('');
|
||||
@@ -134,7 +140,8 @@ export function useStorePickupPage() {
|
||||
!isPageLoading.value &&
|
||||
!isSavingBasic.value &&
|
||||
!isSavingSlots.value &&
|
||||
!isSavingFineRule.value,
|
||||
!isSavingFineRule.value &&
|
||||
!isModeSwitching.value,
|
||||
);
|
||||
|
||||
function clearSettings() {
|
||||
@@ -236,9 +243,43 @@ export function useStorePickupPage() {
|
||||
selectedStoreId.value = value;
|
||||
}
|
||||
|
||||
function setPickupMode(value: 'big' | 'fine') {
|
||||
async function setPickupMode(value: 'big' | 'fine') {
|
||||
if (!canOperate.value) return;
|
||||
if (value === pickupMode.value) return;
|
||||
if (!selectedStoreId.value) return;
|
||||
|
||||
const currentStoreId = selectedStoreId.value;
|
||||
const previousMode = pickupMode.value;
|
||||
pickupMode.value = value;
|
||||
isModeSwitching.value = true;
|
||||
|
||||
try {
|
||||
await savePickupModeApi({
|
||||
storeId: currentStoreId,
|
||||
mode: value,
|
||||
});
|
||||
|
||||
if (selectedStoreId.value === currentStoreId) {
|
||||
isConfigured.value = true;
|
||||
loadedStoreId.value = currentStoreId;
|
||||
snapshot.value = createSettingsSnapshot({
|
||||
mode: value,
|
||||
basicSettings,
|
||||
bigSlots: bigSlots.value,
|
||||
fineRule,
|
||||
previewDays: previewDays.value,
|
||||
});
|
||||
message.success('自提模式已切换');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (selectedStoreId.value === currentStoreId) {
|
||||
pickupMode.value = previousMode;
|
||||
message.error('自提模式切换失败,请稍后重试');
|
||||
}
|
||||
} finally {
|
||||
isModeSwitching.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function setAllowSameDayPickup(value: boolean) {
|
||||
@@ -289,12 +330,14 @@ export function useStorePickupPage() {
|
||||
// 8. 门店切换时自动刷新配置。
|
||||
watch(selectedStoreId, async (storeId) => {
|
||||
if (!storeId) {
|
||||
isModeSwitching.value = false;
|
||||
loadedStoreId.value = '';
|
||||
isConfigured.value = false;
|
||||
clearSettings();
|
||||
snapshot.value = null;
|
||||
return;
|
||||
}
|
||||
isModeSwitching.value = false;
|
||||
loadedStoreId.value = '';
|
||||
isConfigured.value = false;
|
||||
snapshot.value = null;
|
||||
@@ -351,6 +394,7 @@ export function useStorePickupPage() {
|
||||
isPageLoading,
|
||||
isSavingBasic,
|
||||
isSavingFineRule,
|
||||
isModeSwitching,
|
||||
isSavingSlots,
|
||||
isSlotDaySelected,
|
||||
isSlotDrawerOpen,
|
||||
|
||||
@@ -46,6 +46,7 @@ const {
|
||||
isPageLoading,
|
||||
isSavingBasic,
|
||||
isSavingFineRule,
|
||||
isModeSwitching,
|
||||
isSavingSlots,
|
||||
isSlotDaySelected,
|
||||
isSlotDrawerOpen,
|
||||
@@ -129,6 +130,8 @@ const {
|
||||
<PickupModeSwitch
|
||||
:mode="pickupMode"
|
||||
:options="PICKUP_MODE_OPTIONS"
|
||||
:disabled="!canOperate"
|
||||
:is-switching="isModeSwitching"
|
||||
@change="setPickupMode"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
/* 文件职责:自提设置页面模式切换样式。 */
|
||||
.page-store-pickup {
|
||||
.pickup-mode-switch-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pickup-mode-switch {
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 3px;
|
||||
margin-bottom: 16px;
|
||||
background: #f8f9fb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
@@ -21,10 +28,44 @@
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.pickup-mode-item:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.pickup-mode-item.active {
|
||||
font-weight: 600;
|
||||
color: #1677ff;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 2px rgb(15 23 42 / 10%);
|
||||
}
|
||||
|
||||
.pickup-mode-guide {
|
||||
padding: 10px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.pickup-mode-guide-big {
|
||||
background: #f5f9ff;
|
||||
border-color: #bfdbfe;
|
||||
}
|
||||
|
||||
.pickup-mode-guide-fine {
|
||||
background: #effff7;
|
||||
border-color: #bbf7d0;
|
||||
}
|
||||
|
||||
.pickup-mode-guide .guide-title {
|
||||
margin-bottom: 2px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.pickup-mode-guide .guide-desc {
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
color: #4b5563;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,14 @@
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pickup-mode-switch-wrap {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pickup-mode-switch {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.pickup-mode-item {
|
||||
|
||||
Reference in New Issue
Block a user