Files
xk-admin/apps/web-antd/src/components/vip/VipHubModal.vue
李琦 6db736a6f4 feat:
1. 更新框架
2. 修复框架更新后导致的问题
3. 消息系统升级
2026-08-11 13:30:21 +08:00

234 lines
6.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>