234 lines
6.2 KiB
Vue
234 lines
6.2 KiB
Vue
<script lang="ts" setup>
|
||
/**
|
||
* 门店 VIP 入口弹窗
|
||
* 先展示两张操作卡片:开通/升级、开通记录;再分别打开对应子弹窗
|
||
*/
|
||
import { computed, ref } from 'vue';
|
||
|
||
import { useVbenModal } from '@vben/common-ui';
|
||
|
||
import VipBadgeCombo from '#/components/vip/VipBadgeCombo.vue';
|
||
import VipStoreRecordsModal from '#/components/vip/VipStoreRecordsModal.vue';
|
||
import VipUpgradeModal from '#/components/vip/VipUpgradeModal.vue';
|
||
import { formatVipExpireAt } from '#/utils/vip';
|
||
|
||
const gridApi = ref<any>();
|
||
const onSuccess = ref<null | (() => void)>(null);
|
||
const storeId = ref(0);
|
||
const storeName = ref('');
|
||
const vip = ref<Record<string, any> | null>(null);
|
||
|
||
const title = computed(
|
||
() => `VIP管理 - ${storeName.value || storeId.value || ''}`,
|
||
);
|
||
|
||
/** 当前等级 + 时长类型 */
|
||
const currentLevelText = computed(() => {
|
||
const v = vip.value;
|
||
if (!v) return '当前:普通会员';
|
||
const name = String(v.level_name || '').trim();
|
||
const code = String(v.level_code || '').trim();
|
||
const duration = String(v.duration_label || '').trim();
|
||
const level =
|
||
name && code ? `${name}(${code})` : name || code || '普通会员';
|
||
return duration ? `当前:${level} · ${duration}` : `当前:${level}`;
|
||
});
|
||
|
||
/** 到期时间独立一行,打开弹窗时一眼可见 */
|
||
const currentExpireText = computed(() => {
|
||
const v = vip.value;
|
||
if (!v) return '';
|
||
const code = String(v.level_code || 'V0').trim() || 'V0';
|
||
if (code === 'V0') return '';
|
||
const expire = formatVipExpireAt(v, {
|
||
lifetimeText: '终身',
|
||
withPrefix: false,
|
||
});
|
||
if (!expire) return '';
|
||
return expire === '终身' ? '到期时间:终身' : `到期时间:${expire}`;
|
||
});
|
||
|
||
const [UpgradeModalComp, upgradeModalApi] = useVbenModal({
|
||
connectedComponent: VipUpgradeModal,
|
||
});
|
||
const [RecordsModalComp, recordsModalApi] = useVbenModal({
|
||
connectedComponent: VipStoreRecordsModal,
|
||
});
|
||
|
||
const [Modal, modalApi] = useVbenModal({
|
||
fullscreenButton: false,
|
||
draggable: true,
|
||
class: 'w-[640px]',
|
||
showConfirmButton: false,
|
||
cancelText: '关闭',
|
||
onCancel() {
|
||
modalApi.close();
|
||
},
|
||
async onOpenChange(isOpen: boolean) {
|
||
if (!isOpen) {
|
||
storeId.value = 0;
|
||
storeName.value = '';
|
||
vip.value = null;
|
||
gridApi.value = null;
|
||
onSuccess.value = null;
|
||
return;
|
||
}
|
||
const data = modalApi.getData<Record<string, any>>() || {};
|
||
storeId.value = Number(data.store_id || 0);
|
||
storeName.value = String(data.store_name || '');
|
||
vip.value = data.vip || null;
|
||
gridApi.value = data.gridApi || null;
|
||
onSuccess.value = data.onSuccess || null;
|
||
modalApi.setState({ title: title.value });
|
||
},
|
||
});
|
||
|
||
/**
|
||
* 打开开通/升级弹窗
|
||
* 为什么不直接嵌在本弹窗:升级表单已有独立组件,复用可保持交互一致
|
||
*/
|
||
function openUpgrade() {
|
||
if (!storeId.value) return;
|
||
upgradeModalApi.setData({
|
||
store_id: storeId.value,
|
||
store_name: storeName.value,
|
||
vip: vip.value,
|
||
gridApi: gridApi.value,
|
||
onSuccess: () => {
|
||
onSuccess.value?.();
|
||
gridApi.value?.query?.();
|
||
},
|
||
});
|
||
upgradeModalApi.open();
|
||
}
|
||
|
||
/** 打开当前门店开通记录(含开通人) */
|
||
function openRecords() {
|
||
if (!storeId.value) return;
|
||
recordsModalApi.setData({
|
||
store_id: storeId.value,
|
||
store_name: storeName.value,
|
||
});
|
||
recordsModalApi.open();
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<Modal>
|
||
<div class="vip-hub-current mb-4">
|
||
<VipBadgeCombo
|
||
size="md"
|
||
:badge-url="vip?.badge_url"
|
||
:duration-badge-url="vip?.duration_badge_url"
|
||
:level-name="vip?.level_name"
|
||
:duration-label="vip?.duration_label"
|
||
/>
|
||
<div class="vip-hub-current__meta">
|
||
<div class="vip-hub-current__text">{{ currentLevelText }}</div>
|
||
<div v-if="currentExpireText" class="vip-hub-current__expire">
|
||
{{ currentExpireText }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="vip-hub-grid">
|
||
<button type="button" class="vip-hub-card" @click="openUpgrade">
|
||
<div class="vip-hub-card__icon">↑</div>
|
||
<div class="vip-hub-card__title">开通 / 升级</div>
|
||
<div class="vip-hub-card__desc">
|
||
为该门店开通或调整会员等级与时长
|
||
</div>
|
||
</button>
|
||
<button type="button" class="vip-hub-card" @click="openRecords">
|
||
<div class="vip-hub-card__icon">☰</div>
|
||
<div class="vip-hub-card__title">开通记录</div>
|
||
<div class="vip-hub-card__desc">
|
||
查看历史开通流水与开通人
|
||
</div>
|
||
</button>
|
||
</div>
|
||
</Modal>
|
||
<UpgradeModalComp />
|
||
<RecordsModalComp />
|
||
</template>
|
||
|
||
<style scoped>
|
||
.vip-hub-current {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px 14px;
|
||
border: 1px solid hsl(var(--border));
|
||
border-radius: 10px;
|
||
background: hsl(var(--muted) / 0.25);
|
||
}
|
||
.vip-hub-current__meta {
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
.vip-hub-current__text {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: hsl(var(--foreground));
|
||
}
|
||
.vip-hub-current__expire {
|
||
font-size: 12px;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
.vip-hub-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 12px;
|
||
}
|
||
.vip-hub-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
min-height: 148px;
|
||
padding: 18px 16px;
|
||
border: 1px solid hsl(var(--border));
|
||
border-radius: 12px;
|
||
background: hsl(var(--card, var(--background)));
|
||
text-align: left;
|
||
cursor: pointer;
|
||
transition:
|
||
border-color 0.15s,
|
||
background 0.15s,
|
||
box-shadow 0.15s;
|
||
}
|
||
.vip-hub-card:hover {
|
||
border-color: hsl(var(--primary));
|
||
background: hsl(var(--primary) / 0.08);
|
||
box-shadow: 0 0 0 1px hsl(var(--primary) / 0.2);
|
||
}
|
||
.vip-hub-card__icon {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: hsl(var(--primary));
|
||
background: hsl(var(--primary) / 0.12);
|
||
}
|
||
.vip-hub-card__title {
|
||
font-size: 16px;
|
||
font-weight: 650;
|
||
color: hsl(var(--foreground));
|
||
}
|
||
.vip-hub-card__desc {
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
@media (max-width: 560px) {
|
||
.vip-hub-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|