feat(project): 优化费用与自提模式切换交互
This commit is contained in:
@@ -88,6 +88,14 @@ export interface SaveStoreFeesSettingsParams {
|
||||
storeId: string;
|
||||
}
|
||||
|
||||
/** 保存包装费收取方式参数 */
|
||||
export interface SaveStoreFeesModeParams {
|
||||
/** 包装费模式 */
|
||||
packagingFeeMode: PackagingFeeMode;
|
||||
/** 门店 ID */
|
||||
storeId: string;
|
||||
}
|
||||
|
||||
/** 复制费用设置参数 */
|
||||
export interface CopyStoreFeesSettingsParams {
|
||||
sourceStoreId: string;
|
||||
@@ -108,6 +116,11 @@ export async function saveStoreFeesSettingsApi(
|
||||
return requestClient.post<StoreFeesSettingsDto>('/store/fees/save', data);
|
||||
}
|
||||
|
||||
/** 保存包装费收取方式 */
|
||||
export async function saveStoreFeesModeApi(data: SaveStoreFeesModeParams) {
|
||||
return requestClient.post('/store/fees/mode/save', data);
|
||||
}
|
||||
|
||||
/** 复制费用设置到其他门店 */
|
||||
export async function copyStoreFeesSettingsApi(
|
||||
data: CopyStoreFeesSettingsParams,
|
||||
|
||||
@@ -14,6 +14,7 @@ interface Props {
|
||||
formatCurrency: (value: number) => string;
|
||||
formatTierRange: (tier: PackagingFeeTierDto) => string;
|
||||
isSaving: boolean;
|
||||
isSwitchingMode: boolean;
|
||||
onSetFixedPackagingFee: (value: number) => void;
|
||||
onSetPackagingMode: (value: 'item' | 'order') => void;
|
||||
onSetTieredEnabled: (value: boolean) => void;
|
||||
@@ -36,6 +37,20 @@ function toNumber(value: null | number | string, fallback = 0) {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : fallback;
|
||||
}
|
||||
|
||||
function getModeLabel(mode: 'item' | 'order') {
|
||||
return mode === 'order' ? '按订单收取' : '按商品收取';
|
||||
}
|
||||
|
||||
function getModeHint(mode: 'item' | 'order') {
|
||||
return mode === 'order' ? '按订单金额统一计算包装费' : '按商品维度汇总包装费';
|
||||
}
|
||||
|
||||
function getModeEffect(mode: 'item' | 'order') {
|
||||
return mode === 'order'
|
||||
? '固定包装费/阶梯包装费生效,商品包装费暂不生效。'
|
||||
: '商品包装费生效,本页固定包装费/阶梯包装费暂不生效。';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -44,50 +59,50 @@ function toNumber(value: null | number | string, fallback = 0) {
|
||||
<span class="section-title">包装费设置</span>
|
||||
</template>
|
||||
|
||||
<div class="packaging-mode-toggle-row">
|
||||
<button
|
||||
type="button"
|
||||
class="mode-toggle-label"
|
||||
:class="{ active: props.packagingMode === 'order' }"
|
||||
:disabled="!props.canOperate"
|
||||
@click="props.onSetPackagingMode('order')"
|
||||
>
|
||||
按订单收取
|
||||
</button>
|
||||
|
||||
<Switch
|
||||
:checked="props.packagingMode === 'item'"
|
||||
:disabled="!props.canOperate"
|
||||
@update:checked="
|
||||
(checked) => props.onSetPackagingMode(checked ? 'item' : 'order')
|
||||
"
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="mode-toggle-label"
|
||||
:class="{ active: props.packagingMode === 'item' }"
|
||||
:disabled="!props.canOperate"
|
||||
@click="props.onSetPackagingMode('item')"
|
||||
>
|
||||
按商品收取
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="packaging-mode-guide"
|
||||
:class="
|
||||
props.packagingMode === 'item'
|
||||
? 'packaging-mode-guide-item'
|
||||
: 'packaging-mode-guide-order'
|
||||
"
|
||||
>
|
||||
<div class="guide-title">当前生效规则</div>
|
||||
<div v-if="props.packagingMode === 'order'" class="guide-desc">
|
||||
订单将按固定/阶梯包装费计算;商品包装费配置暂不生效。
|
||||
<div class="packaging-mode-panel">
|
||||
<div class="packaging-mode-header">
|
||||
<div class="packaging-mode-title">包装费收取方式</div>
|
||||
<div class="packaging-mode-badge">
|
||||
{{
|
||||
props.isSwitchingMode
|
||||
? '切换中...'
|
||||
: `当前:${getModeLabel(props.packagingMode)}`
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="guide-desc">
|
||||
订单包装费将汇总商品维度配置;本页固定/阶梯包装费暂不生效。
|
||||
|
||||
<div class="packaging-mode-grid">
|
||||
<button
|
||||
type="button"
|
||||
class="packaging-mode-card"
|
||||
:class="{ active: props.packagingMode === 'order' }"
|
||||
:disabled="!props.canOperate || props.isSwitchingMode"
|
||||
@click="props.onSetPackagingMode('order')"
|
||||
>
|
||||
<div class="packaging-mode-card-title">
|
||||
<span class="packaging-mode-dot"></span>
|
||||
{{ getModeLabel('order') }}
|
||||
</div>
|
||||
<div class="packaging-mode-card-hint">{{ getModeHint('order') }}</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="packaging-mode-card"
|
||||
:class="{ active: props.packagingMode === 'item' }"
|
||||
:disabled="!props.canOperate || props.isSwitchingMode"
|
||||
@click="props.onSetPackagingMode('item')"
|
||||
>
|
||||
<div class="packaging-mode-card-title">
|
||||
<span class="packaging-mode-dot"></span>
|
||||
{{ getModeLabel('item') }}
|
||||
</div>
|
||||
<div class="packaging-mode-card-hint">{{ getModeHint('item') }}</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="packaging-mode-effect">
|
||||
切换后效果:{{ getModeEffect(props.packagingMode) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue';
|
||||
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
import { saveStoreFeesModeApi } from '#/api/store-fees';
|
||||
|
||||
import { PACKAGING_MODE_OPTIONS } from './fees-page/constants';
|
||||
import { createCopyActions } from './fees-page/copy-actions';
|
||||
import { createDataActions } from './fees-page/data-actions';
|
||||
@@ -24,6 +26,7 @@ import {
|
||||
cloneFeesForm,
|
||||
cloneOtherFees,
|
||||
cloneTiers,
|
||||
createSettingsSnapshot,
|
||||
formatCurrency,
|
||||
formatTierRange,
|
||||
normalizeMoney,
|
||||
@@ -49,8 +52,6 @@ const EMPTY_FEES_SETTINGS: StoreFeesFormState = {
|
||||
},
|
||||
},
|
||||
};
|
||||
const PACKAGING_MODE_SWITCH_CONFIRM_KEY =
|
||||
'store-fees-packaging-mode-switch-confirmed';
|
||||
|
||||
export function useStoreFeesPage() {
|
||||
// 1. 页面 loading / submitting 状态。
|
||||
@@ -59,6 +60,7 @@ export function useStoreFeesPage() {
|
||||
const isSavingDelivery = ref(false);
|
||||
const isSavingPackaging = ref(false);
|
||||
const isSavingOther = ref(false);
|
||||
const isSwitchingPackagingMode = ref(false);
|
||||
const isCopySubmitting = ref(false);
|
||||
const isConfigured = ref(false);
|
||||
|
||||
@@ -104,7 +106,8 @@ export function useStoreFeesPage() {
|
||||
!isPageLoading.value &&
|
||||
!isSavingDelivery.value &&
|
||||
!isSavingPackaging.value &&
|
||||
!isSavingOther.value,
|
||||
!isSavingOther.value &&
|
||||
!isSwitchingPackagingMode.value,
|
||||
);
|
||||
|
||||
const copyCandidates = computed(() =>
|
||||
@@ -129,29 +132,11 @@ export function useStoreFeesPage() {
|
||||
|
||||
const isOrderMode = computed(() => form.packagingFeeMode === 'order');
|
||||
|
||||
function hasConfirmedPackagingModeSwitch() {
|
||||
try {
|
||||
return (
|
||||
window.localStorage.getItem(PACKAGING_MODE_SWITCH_CONFIRM_KEY) === '1'
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function markPackagingModeSwitchConfirmed() {
|
||||
try {
|
||||
window.localStorage.setItem(PACKAGING_MODE_SWITCH_CONFIRM_KEY, '1');
|
||||
} catch {
|
||||
// 忽略本地存储异常,不影响当前切换。
|
||||
}
|
||||
}
|
||||
|
||||
function getPackagingModeConfirmContent(value: PackagingFeeMode) {
|
||||
if (value === 'order') {
|
||||
return '切换后订单将按本页固定/阶梯包装费计算,商品包装费配置将暂不生效。';
|
||||
return '切换后将按订单收取包装费,固定/阶梯包装费立即生效;商品维度包装费将暂不生效。';
|
||||
}
|
||||
return '切换后订单包装费将汇总商品维度配置,本页固定/阶梯包装费将暂不生效。';
|
||||
return '切换后将按商品收取包装费,商品配置的包装费立即生效;本页固定/阶梯包装费将暂不生效。';
|
||||
}
|
||||
|
||||
function clearSettings() {
|
||||
@@ -237,21 +222,52 @@ export function useStoreFeesPage() {
|
||||
function setPackagingMode(value: PackagingFeeMode) {
|
||||
if (!canOperate.value) return;
|
||||
if (value === form.packagingFeeMode) return;
|
||||
if (!selectedStoreId.value) return;
|
||||
|
||||
const applyMode = () => setPackagingFeeMode(value);
|
||||
if (hasConfirmedPackagingModeSwitch()) {
|
||||
applyMode();
|
||||
return;
|
||||
}
|
||||
const currentStoreId = selectedStoreId.value;
|
||||
const previousMode = form.packagingFeeMode;
|
||||
const previousOrderMode = form.orderPackagingFeeMode;
|
||||
|
||||
Modal.confirm({
|
||||
title: '确认切换包装费收取方式?',
|
||||
content: getPackagingModeConfirmContent(value),
|
||||
okText: '确认切换',
|
||||
cancelText: '取消',
|
||||
onOk() {
|
||||
applyMode();
|
||||
markPackagingModeSwitchConfirmed();
|
||||
async onOk() {
|
||||
if (isSwitchingPackagingMode.value) return;
|
||||
isSwitchingPackagingMode.value = true;
|
||||
setPackagingFeeMode(value);
|
||||
|
||||
try {
|
||||
await saveStoreFeesModeApi({
|
||||
storeId: currentStoreId,
|
||||
packagingFeeMode: value,
|
||||
});
|
||||
|
||||
if (selectedStoreId.value === currentStoreId) {
|
||||
isConfigured.value = true;
|
||||
loadedStoreId.value = currentStoreId;
|
||||
if (snapshot.value) {
|
||||
snapshot.value = {
|
||||
...snapshot.value,
|
||||
packagingFeeMode: form.packagingFeeMode,
|
||||
orderPackagingFeeMode: form.orderPackagingFeeMode,
|
||||
};
|
||||
} else {
|
||||
snapshot.value = createSettingsSnapshot(form);
|
||||
}
|
||||
message.success('包装费收取方式已切换');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (selectedStoreId.value === currentStoreId) {
|
||||
form.packagingFeeMode = previousMode;
|
||||
form.orderPackagingFeeMode = previousOrderMode;
|
||||
}
|
||||
message.error('包装费收取方式切换失败,请稍后重试');
|
||||
} finally {
|
||||
isSwitchingPackagingMode.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -369,6 +385,7 @@ export function useStoreFeesPage() {
|
||||
|
||||
/** 切换门店时同步拉取配置。 */
|
||||
watch(selectedStoreId, async (storeId) => {
|
||||
isSwitchingPackagingMode.value = false;
|
||||
if (!storeId) {
|
||||
clearSettings();
|
||||
isConfigured.value = false;
|
||||
@@ -407,6 +424,7 @@ export function useStoreFeesPage() {
|
||||
isSavingDelivery,
|
||||
isSavingOther,
|
||||
isSavingPackaging,
|
||||
isSwitchingPackagingMode,
|
||||
isStoreLoading,
|
||||
isTierDrawerOpen,
|
||||
loadedStoreId,
|
||||
|
||||
@@ -38,6 +38,7 @@ const {
|
||||
isSavingDelivery,
|
||||
isSavingOther,
|
||||
isSavingPackaging,
|
||||
isSwitchingPackagingMode,
|
||||
isStoreLoading,
|
||||
isTierDrawerOpen,
|
||||
onDeleteTier,
|
||||
@@ -126,6 +127,7 @@ function onEditTier(tier: PackagingFeeTierDto) {
|
||||
:fixed-packaging-fee="form.fixedPackagingFee"
|
||||
:tiers="form.packagingFeeTiers"
|
||||
:is-saving="isSavingPackaging"
|
||||
:is-switching-mode="isSwitchingPackagingMode"
|
||||
:format-currency="formatCurrency"
|
||||
:format-tier-range="formatTierRange"
|
||||
:on-set-packaging-mode="setPackagingMode"
|
||||
|
||||
@@ -1,58 +1,110 @@
|
||||
/* 文件职责:包装费卡片样式。 */
|
||||
.page-store-fees {
|
||||
.packaging-mode-toggle-row {
|
||||
display: inline-flex;
|
||||
.packaging-mode-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
margin-bottom: 16px;
|
||||
background: #fff;
|
||||
border: 1px solid #e5e9f0;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.packaging-mode-header {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.mode-toggle-label {
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
color: #4b5563;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.mode-toggle-label.active {
|
||||
font-weight: 600;
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
.mode-toggle-label:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.packaging-mode-guide {
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #dbeafe;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.packaging-mode-guide-order {
|
||||
background: #f5f9ff;
|
||||
}
|
||||
|
||||
.packaging-mode-guide-item {
|
||||
background: #f6ffed;
|
||||
border-color: #ccebd2;
|
||||
}
|
||||
|
||||
.packaging-mode-guide .guide-title {
|
||||
margin-bottom: 4px;
|
||||
font-size: 12px;
|
||||
.packaging-mode-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.packaging-mode-guide .guide-desc {
|
||||
.packaging-mode-badge {
|
||||
padding: 3px 10px;
|
||||
font-size: 12px;
|
||||
color: #4b5563;
|
||||
line-height: 1.5;
|
||||
color: #155eef;
|
||||
background: #eff4ff;
|
||||
border: 1px solid #c7d7fe;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.packaging-mode-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.packaging-mode-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-height: 92px;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
border: 1px solid #d8dde6;
|
||||
border-radius: 10px;
|
||||
transition: all 0.18s ease;
|
||||
}
|
||||
|
||||
.packaging-mode-card:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.packaging-mode-card:hover:not(:disabled) {
|
||||
border-color: #86a6ff;
|
||||
box-shadow: 0 4px 12px rgb(17 24 39 / 8%);
|
||||
}
|
||||
|
||||
.packaging-mode-card.active {
|
||||
background: linear-gradient(160deg, #ecf3ff 0%, #f8fbff 100%);
|
||||
border-color: #5b8bff;
|
||||
box-shadow: 0 6px 18px rgb(32 94 255 / 15%);
|
||||
}
|
||||
|
||||
.packaging-mode-card-title {
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.packaging-mode-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #94a3b8;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.packaging-mode-card.active .packaging-mode-dot {
|
||||
background: #2563eb;
|
||||
box-shadow: 0 0 0 4px rgb(37 99 235 / 16%);
|
||||
}
|
||||
|
||||
.packaging-mode-card-hint {
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
color: #556070;
|
||||
}
|
||||
|
||||
.packaging-mode-effect {
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #1f3a8a;
|
||||
background: #f5f8ff;
|
||||
border: 1px solid #dbe7ff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.packaging-tier-block {
|
||||
|
||||
@@ -23,14 +23,25 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.packaging-mode-toggle-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.packaging-mode-panel {
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.packaging-mode-header {
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.packaging-mode-grid {
|
||||
grid-template-columns: 1fr;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mode-toggle-label {
|
||||
text-align: center;
|
||||
.packaging-mode-card {
|
||||
min-height: 84px;
|
||||
padding: 10px 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
*/
|
||||
import type { PickupMode } from '#/api/store-pickup';
|
||||
|
||||
import { Switch } from 'ant-design-vue';
|
||||
|
||||
interface Props {
|
||||
disabled?: boolean;
|
||||
isSwitching?: boolean;
|
||||
@@ -24,54 +22,53 @@ const emit = defineEmits<{
|
||||
function getModeLabel(mode: PickupMode) {
|
||||
return props.options.find((item) => item.value === mode)?.label ?? mode;
|
||||
}
|
||||
|
||||
function getModeHint(mode: PickupMode) {
|
||||
return mode === 'big'
|
||||
? '按时段统一预约,适合固定营业节奏'
|
||||
: '按精细时间点预约,适合高峰分流';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="pickup-mode-switch-wrap">
|
||||
<div class="pickup-mode-switch">
|
||||
<div class="pickup-mode-panel" :class="{ switching: props.isSwitching }">
|
||||
<div class="pickup-mode-header">
|
||||
<div class="pickup-mode-title">自提预约模式</div>
|
||||
<div class="pickup-mode-badge">
|
||||
{{
|
||||
props.isSwitching ? '切换中...' : `当前:${getModeLabel(props.mode)}`
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pickup-mode-grid">
|
||||
<button
|
||||
type="button"
|
||||
class="pickup-mode-item"
|
||||
class="pickup-mode-card"
|
||||
:class="{ active: props.mode === 'big' }"
|
||||
:disabled="props.disabled || props.isSwitching"
|
||||
@click="emit('change', 'big')"
|
||||
>
|
||||
{{ getModeLabel('big') }}
|
||||
<div class="pickup-mode-card-title">
|
||||
<span class="pickup-mode-dot"></span>
|
||||
{{ getModeLabel('big') }}
|
||||
</div>
|
||||
<div class="pickup-mode-card-hint">{{ getModeHint('big') }}</div>
|
||||
</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="pickup-mode-card"
|
||||
:class="{ active: props.mode === 'fine' }"
|
||||
:disabled="props.disabled || props.isSwitching"
|
||||
@click="emit('change', 'fine')"
|
||||
>
|
||||
{{ getModeLabel('fine') }}
|
||||
<div class="pickup-mode-card-title">
|
||||
<span class="pickup-mode-dot"></span>
|
||||
{{ getModeLabel('fine') }}
|
||||
</div>
|
||||
<div class="pickup-mode-card-hint">{{ getModeHint('fine') }}</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="pickup-mode-guide"
|
||||
:class="
|
||||
props.mode === 'fine'
|
||||
? 'pickup-mode-guide-fine'
|
||||
: 'pickup-mode-guide-big'
|
||||
"
|
||||
>
|
||||
<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,7 +18,7 @@ import type {
|
||||
|
||||
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
import { savePickupModeApi } from '#/api/store-pickup';
|
||||
|
||||
@@ -243,43 +243,67 @@ export function useStorePickupPage() {
|
||||
selectedStoreId.value = value;
|
||||
}
|
||||
|
||||
async function setPickupMode(value: 'big' | 'fine') {
|
||||
function getPickupModeConfirmContent(value: 'big' | 'fine') {
|
||||
if (value === 'big') {
|
||||
return '切换后将按大时段统一预约,精细时段规则与预览将暂不生效。';
|
||||
}
|
||||
return '切换后将按精细时段预约,大时段配置将暂不生效。';
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
Modal.confirm({
|
||||
title: '确认切换自提预约模式?',
|
||||
content: getPickupModeConfirmContent(value),
|
||||
okText: '确认切换',
|
||||
cancelText: '取消',
|
||||
async onOk() {
|
||||
if (isModeSwitching.value) return;
|
||||
isModeSwitching.value = true;
|
||||
pickupMode.value = 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;
|
||||
}
|
||||
try {
|
||||
await savePickupModeApi({
|
||||
storeId: currentStoreId,
|
||||
mode: value,
|
||||
});
|
||||
|
||||
if (selectedStoreId.value === currentStoreId) {
|
||||
isConfigured.value = true;
|
||||
loadedStoreId.value = currentStoreId;
|
||||
if (snapshot.value) {
|
||||
snapshot.value = {
|
||||
...snapshot.value,
|
||||
mode: value,
|
||||
};
|
||||
} else {
|
||||
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) {
|
||||
|
||||
@@ -1,71 +1,105 @@
|
||||
/* 文件职责:自提设置页面模式切换样式。 */
|
||||
.page-store-pickup {
|
||||
.pickup-mode-switch-wrap {
|
||||
.pickup-mode-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
margin-bottom: 16px;
|
||||
background: #fff;
|
||||
border: 1px solid #e5e9f0;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.pickup-mode-switch {
|
||||
display: inline-flex;
|
||||
.pickup-mode-header {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 3px;
|
||||
background: #f8f9fb;
|
||||
border-radius: 8px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.pickup-mode-item {
|
||||
min-width: 118px;
|
||||
padding: 6px 18px;
|
||||
font-size: 13px;
|
||||
color: #4b5563;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
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: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.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;
|
||||
.pickup-mode-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.pickup-mode-guide .guide-desc {
|
||||
.pickup-mode-badge {
|
||||
padding: 3px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #155eef;
|
||||
background: #eff4ff;
|
||||
border: 1px solid #c7d7fe;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.pickup-mode-panel.switching .pickup-mode-badge {
|
||||
color: #9a3412;
|
||||
background: #fff7ed;
|
||||
border-color: #fdba74;
|
||||
}
|
||||
|
||||
.pickup-mode-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.pickup-mode-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-height: 92px;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
border: 1px solid #d8dde6;
|
||||
border-radius: 10px;
|
||||
transition: all 0.18s ease;
|
||||
}
|
||||
|
||||
.pickup-mode-card:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.pickup-mode-card:hover:not(:disabled) {
|
||||
border-color: #86a6ff;
|
||||
box-shadow: 0 4px 12px rgb(17 24 39 / 8%);
|
||||
}
|
||||
|
||||
.pickup-mode-card.active {
|
||||
background: linear-gradient(160deg, #ecf3ff 0%, #f8fbff 100%);
|
||||
border-color: #5b8bff;
|
||||
box-shadow: 0 6px 18px rgb(32 94 255 / 15%);
|
||||
}
|
||||
|
||||
.pickup-mode-card-title {
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.pickup-mode-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #94a3b8;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.pickup-mode-card.active .pickup-mode-dot {
|
||||
background: #2563eb;
|
||||
box-shadow: 0 0 0 4px rgb(37 99 235 / 16%);
|
||||
}
|
||||
|
||||
.pickup-mode-card-hint {
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
color: #4b5563;
|
||||
color: #556070;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,20 +7,25 @@
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pickup-mode-switch-wrap {
|
||||
gap: 8px;
|
||||
.pickup-mode-panel {
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.pickup-mode-switch {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.pickup-mode-header {
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.pickup-mode-grid {
|
||||
grid-template-columns: 1fr;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pickup-mode-item {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
.pickup-mode-card {
|
||||
min-height: 84px;
|
||||
padding: 10px 11px;
|
||||
}
|
||||
|
||||
.pickup-form-row {
|
||||
|
||||
Reference in New Issue
Block a user