| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- import { defineComponent, ref, reactive, computed, onUnmounted } from 'vue';
- import { NButton, NSpin } from 'naive-ui';
- import styles from './index.module.less';
- import TheQrCode from '@/components/TheQrCode';
- import qs from 'query-string';
- import {
- getPaymentConfig,
- createVipOrder,
- getOrderDetail
- } from '@/api/payment';
- import { useUserStore } from '@/store/modules/users';
- import { getHttpOrigin } from '/src/helpers/utils';
- // API返回的套餐类型
- interface VipPackageApiItem {
- id: number;
- title: string;
- number: number;
- unit: 'DAY' | 'MONTH' | 'YEAR';
- originalPrice: number;
- currentPrice: number;
- free?: {
- number: number;
- unit: 'DAY' | 'MONTH' | 'YEAR';
- };
- }
- // UI使用的套餐类型
- interface VipPackage {
- id: number;
- name: string;
- price: number;
- days: number;
- originalPrice: number;
- freeText?: string; // 赠送信息
- }
- export default defineComponent({
- name: 'VipPurchaseModal',
- props: {
- /** 是否有取消按钮 */
- hasCancel: {
- type: Boolean,
- default: false
- }
- },
- emits: ['close', 'success'],
- setup(_, { emit }) {
- const userStore = useUserStore();
- // VIP套餐列表(从API获取)
- const vipPackages = ref<VipPackage[]>([]);
- const selectedPackageId = ref<number>(0);
- const loading = ref(false);
- const hasClicked = ref(false); // 防重复点击标记
- const pageLoading = ref(true); // 页面初始加载状态
- const showQrCode = ref(false);
- const qrCodeUrl = ref('');
- const orderNo = ref('');
- const pollingTimer = ref<number | null>(null);
- // 支付配置
- const paymentConfig = reactive({
- paymentType: '',
- paymentChannel: '',
- wxAppId: ''
- });
- // 计算总天数
- const calculateDays = (item: VipPackageApiItem): number => {
- let days = 0;
- // 主时长
- switch (item.unit) {
- case 'DAY':
- days += item.number;
- break;
- case 'MONTH':
- days += item.number * 30;
- break;
- case 'YEAR':
- days += item.number * 365;
- break;
- }
- // 赠送时长
- if (item.free) {
- switch (item.free.unit) {
- case 'DAY':
- days += item.free.number;
- break;
- case 'MONTH':
- days += item.free.number * 30;
- break;
- case 'YEAR':
- days += item.free.number * 365;
- break;
- }
- }
- return days;
- };
- // 生成赠送信息
- const generateFreeText = (item: VipPackageApiItem): string | undefined => {
- if (!item.free || item.free.number <= 0) return undefined;
- const freeUnit =
- item.free.unit === 'DAY'
- ? '天'
- : item.free.unit === 'MONTH'
- ? '个月'
- : '年';
- return `赠送${item.free.number}${freeUnit}`;
- };
- // 选中的套餐信息
- const currentPackage = computed(
- () =>
- vipPackages.value.find(p => p.id === selectedPackageId.value) ||
- vipPackages.value[0]
- );
- // 获取支付配置和VIP套餐
- const fetchPaymentConfig = async () => {
- try {
- const { data } = await getPaymentConfig();
- if (data && Array.isArray(data)) {
- data.forEach((item: any) => {
- if (item.paramName === 'payment_service_provider') {
- const provider = JSON.parse(item.paramValue);
- paymentConfig.paymentType = provider.vendor;
- paymentConfig.paymentChannel = provider.channel;
- paymentConfig.wxAppId = provider.wxAppId || '';
- }
- if (
- item.paramName === 'teacher_vip_purchase_list' &&
- item.paramValue
- ) {
- const packageList: VipPackageApiItem[] = JSON.parse(
- item.paramValue
- );
- vipPackages.value = packageList.map(pkg => ({
- id: pkg.id,
- name: pkg.title,
- price: pkg.currentPrice,
- days: calculateDays(pkg),
- originalPrice: pkg.originalPrice,
- freeText: generateFreeText(pkg)
- }));
- // 默认选中中间的那个套餐
- const defaultIndex = Math.floor(vipPackages.value.length / 2);
- selectedPackageId.value =
- vipPackages.value[defaultIndex]?.id || 1;
- }
- });
- }
- } catch (e) {
- console.error('获取支付配置失败', e);
- } finally {
- pageLoading.value = false;
- }
- };
- // 开始轮询订单状态
- const startPolling = () => {
- if (pollingTimer.value) {
- clearInterval(pollingTimer.value);
- }
- pollingTimer.value = window.setInterval(async () => {
- try {
- const { data } = await getOrderDetail(orderNo.value);
- if (data && data.status === 'PAID') {
- stopPolling();
- window.$message.success('支付成功');
- handleCancel();
- await userStore.getInfo();
- emit('success');
- emit('close');
- }
- } catch (e) {
- console.error('轮询订单状态失败', e);
- }
- }, 3000);
- };
- // 停止轮询
- const stopPolling = () => {
- if (pollingTimer.value) {
- clearInterval(pollingTimer.value);
- pollingTimer.value = null;
- }
- };
- // 取消支付
- const handleCancel = () => {
- stopPolling();
- showQrCode.value = false;
- qrCodeUrl.value = '';
- orderNo.value = '';
- hasClicked.value = false;
- };
- // 创建订单并生成二维码链接
- const handlePurchase = async () => {
- if (hasClicked.value || loading.value) return;
- hasClicked.value = true;
- loading.value = true;
- try {
- const pkg = currentPackage.value;
- const goodsInfos = [
- {
- goodsId: pkg.id,
- goodsName: pkg.name,
- goodsPrice: pkg.price,
- goodsNum: 1,
- goodsType: 'TEACHER_VIP'
- }
- ];
- // 创建订单
- const orderRes = await createVipOrder({
- orderType: 'TEACHER_VIP',
- paymentType: paymentConfig.paymentType,
- paymentChannel: paymentConfig.paymentChannel,
- paymentCashAmount: pkg.price,
- paymentCouponAmount: 0,
- goodsInfos,
- orderName: '乐器AI学练工具',
- orderDesc: '乐器AI学练工具'
- });
- if (orderRes.data) {
- orderNo.value = orderRes.data.orderNo;
- const payConfig =
- orderRes.data.paymentConfig?.paymentConfig ||
- orderRes.data.paymentConfig;
- // 生成二维码链接,跳转到 classroom-app 的 payDefine 页面
- const params = qs.stringify({
- pay_channel: paymentConfig.paymentChannel,
- wxAppId: payConfig.wxAppId,
- alipayAppId: payConfig.alipayAppId || '',
- paymentType: paymentConfig.paymentType,
- body: '乐器AI学练工具',
- price: pkg.price,
- orderNo: payConfig.merOrderNo || orderNo.value,
- userId: payConfig.userId
- });
- // getHttpOrigin() +
- qrCodeUrl.value =
- getHttpOrigin() + '/classroom-app/#/payDefine?' + params;
- console.log(qrCodeUrl.value);
- showQrCode.value = true;
- console.log(qrCodeUrl.value, 'value');
- startPolling();
- }
- } catch (e: any) {
- hasClicked.value = false;
- window.$message.error(e.msg || '创建订单失败');
- } finally {
- loading.value = false;
- }
- };
- // 初始化获取支付配置
- fetchPaymentConfig();
- // 组件卸载时清理
- onUnmounted(() => {
- stopPolling();
- });
- // 计算会员是否过期
- const membershipEndTime = userStore.getUserInfo?.membershipEndTime;
- const isVipExpired = computed(() => {
- return !membershipEndTime || new Date(membershipEndTime) < new Date();
- });
- return () => (
- <NSpin show={pageLoading.value} class={styles.spinWrap}>
- <div class={styles.vipPurchaseModal}>
- {!showQrCode.value ? (
- <div>
- {isVipExpired.value && (
- <div class={styles.subtitle}>
- 您的会员已过期,请续费后继续使用
- </div>
- )}
- <div class={styles.packageList}>
- {vipPackages.value.map((pkg: VipPackage) => (
- <div
- key={pkg.id}
- class={[
- styles.packageItem,
- selectedPackageId.value === pkg.id ? styles.selected : ''
- ]}
- onClick={() => (selectedPackageId.value = pkg.id)}
- >
- <div class={styles.packageName}>{pkg.name}</div>
- <div class={styles.freeText}>{pkg.freeText || ''}</div>
- <div class={styles.packagePrice}>
- <span class={styles.priceAmount}>{pkg.price}</span>
- <del class={styles.originalPrice}>
- ¥{pkg.originalPrice}
- </del>
- </div>
- </div>
- ))}
- </div>
- <div class={styles.btnGroup}>
- <NButton round size="large" onClick={() => emit('close')}>
- 取消
- </NButton>
- <NButton
- round
- size="large"
- type="primary"
- loading={loading.value}
- disabled={loading.value || hasClicked.value}
- onClick={handlePurchase}
- >
- 续费 ¥{currentPackage.value?.price}
- </NButton>
- </div>
- </div>
- ) : (
- <div class={styles.qrCodeSection}>
- <div class={styles.qrCodeTitle}>扫码支付</div>
- <div class={styles.qrCodeWrap}>
- {qrCodeUrl.value && (
- <TheQrCode text={qrCodeUrl.value} size={200} />
- )}
- <div class={styles.payTip}>请使用微信扫描二维码完成支付</div>
- <div class={styles.orderInfo}>订单号:{orderNo.value}</div>
- </div>
- <div class={styles.btnGroup}>
- <NButton round size="large" onClick={handleCancel}>
- 取消支付
- </NButton>
- </div>
- </div>
- )}
- </div>
- </NSpin>
- );
- }
- });
|