jcloud/dashboard/src2/components/AppTrialSubscriptionDialog.vue
2025-06-27 18:50:23 +08:00

365 lines
10 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.

<template>
<Dialog
:options="{
title: '立即订阅',
size: '2xl'
}"
v-model="showDialog"
>
<template v-slot:body-content>
<div class="border-0">
<p class="text-base text-gray-800">
您距离创建第一个网站仅几步之遥
</p>
<div class="mt-6 space-y-6">
<!-- 第一步选择计划 -->
<div>
<div v-if="step == 1">
<div class="flex items-center space-x-2">
<TextInsideCircle>1</TextInsideCircle>
<span class="text-base font-medium"> 选择网站计划 </span>
</div>
<div class="pl-7">
<SitePlansCards
:hideRestrictedPlans="true"
v-model="selectedPlan"
class="mt-4"
/>
<p></p>
<div class="flex w-full justify-end">
<Button
class="mt-2 w-full sm:w-fit"
variant="solid"
@click="confirmPlan"
>
确认计划
</Button>
</div>
</div>
</div>
<div v-else>
<div class="flex items-center justify-between space-x-2">
<div class="flex items-center space-x-2">
<TextInsideCircle>1</TextInsideCircle>
<span class="text-base font-medium">
已选择网站计划 ({{ selectedPlan.name }})
</span>
</div>
<div
class="grid h-4 w-4 place-items-center rounded-full bg-green-500/90"
>
<i-lucide-check class="h-3 w-3 text-white" />
</div>
</div>
</div>
</div>
<!-- 第二步更新账单信息 -->
<div
class="rounded-md"
:class="{ 'pointer-events-none opacity-50': step < 2 }"
>
<div v-if="step <= 2">
<div class="flex items-center space-x-2">
<TextInsideCircle>2</TextInsideCircle>
<span class="text-base font-medium">
更新账单信息
</span>
</div>
<div class="pl-7" v-if="step == 2">
<UpdateBillingDetailsForm
@updated="onBillingAddresUpdateSuccess"
/>
</div>
</div>
<div v-else>
<div class="flex items-center justify-between space-x-2">
<div class="flex items-center space-x-2">
<TextInsideCircle>2</TextInsideCircle>
<span class="text-base font-medium">
账单信息已更新
</span>
</div>
<div
class="grid h-4 w-4 place-items-center rounded-full bg-green-500/90"
>
<i-lucide-check class="h-3 w-3 text-white" />
</div>
</div>
</div>
</div>
<!-- 第三步添加支付方式 -->
<div
class="rounded-md"
:class="{ 'pointer-events-none opacity-50': step < 3 }"
>
<div v-if="step <= 3">
<div class="flex items-center space-x-2">
<TextInsideCircle>3</TextInsideCircle>
<span class="text-base font-medium"> 添加支付方式 </span>
</div>
<div class="mt-4 pl-7" v-if="step == 3">
<!-- 支付方式选择器 -->
<div
class="flex w-full flex-row gap-2 rounded-md border p-1 text-p-base text-gray-800"
>
<div
class="w-1/2 cursor-pointer rounded-sm py-1.5 text-center transition-all"
:class="{
'bg-gray-100': isAutomatedBilling
}"
@click="isAutomatedBilling = true"
>
自动扣款
</div>
<div
class="w-1/2 cursor-pointer rounded-sm py-1.5 text-center transition-all"
:class="{
'bg-gray-100': !isAutomatedBilling
}"
@click="isAutomatedBilling = false"
>
充值
</div>
</div>
<div class="mt-2 w-full">
<!-- 自动计费部分 -->
<div v-if="isAutomatedBilling">
<!-- Stripe -->
<StripeCard2
@complete="onAddCardSuccess"
:withoutAddress="true"
/>
</div>
<!-- 购买预付费信用 -->
<div v-else class="mt-3">
<BuyPrepaidCreditsForm
:isOnboarding="true"
:minimumAmount="minimumAmount"
@success="onBuyCreditsSuccess"
/>
</div>
</div>
</div>
</div>
<!-- 支付方式已添加 -->
<div v-else>
<div class="flex items-center justify-between space-x-2">
<div class="flex items-center space-x-2">
<TextInsideCircle>3</TextInsideCircle>
<span
class="text-base font-medium"
v-if="$team.pg.payment_mode === 'Card'"
>
自动计费设置已完成
</span>
<span
class="text-base font-medium"
v-if="$team.pg.payment_mode === 'Prepaid Credits'"
>
余额支付已开通
</span>
</div>
<div
class="grid h-4 w-4 place-items-center rounded-full bg-green-500/90"
>
<i-lucide-check class="h-3 w-3 text-white" />
</div>
</div>
<div
class="mt-1.5 pl-7 text-p-base text-gray-800"
v-if="$team.pg.payment_mode === 'Prepaid Credits'"
>
账户余额: {{ $format.userCurrency($team.pg.balance) }}
</div>
</div>
</div>
<!-- 第4步确认 -->
<div>
<div
v-if="step < 4"
class="pointer-events-none rounded-md opacity-50"
>
<div class="flex items-center justify-between space-x-2">
<div class="flex items-center space-x-2">
<TextInsideCircle>4</TextInsideCircle>
<div class="text-base font-medium">
订阅确认
</div>
</div>
</div>
</div>
<div v-else-if="isChangingPlan">
<div class="flex items-center justify-between space-x-2">
<div class="flex items-center space-x-2">
<TextInsideCircle>4</TextInsideCircle>
<div class="text-base font-medium">正在更新站点计划</div>
</div>
</div>
<div class="mt-3 pl-7">
<Button :loading="true" loadingText="正在更新站点计划...">
站点计划已更新
</Button>
</div>
</div>
<div v-else>
<div class="flex items-center justify-between space-x-2">
<div class="flex items-center space-x-2">
<TextInsideCircle>4</TextInsideCircle>
<span class="text-base font-medium">
🎉 订阅已确认
</span>
</div>
<div
class="grid h-4 w-4 place-items-center rounded-full bg-green-500/90"
>
<i-lucide-check class="h-3 w-3 text-white" />
</div>
</div>
<div class="mt-1.5 pl-7">
<div class="flex items-center space-x-2">
<span class="text-p-base text-gray-800">
您的订阅已确认<br />
如果您的任何站点已被禁用它很快就会重新启用
</span>
</div>
<div class="flex w-full justify-end">
<Button
class="mt-2 w-full sm:w-fit"
variant="solid"
iconRight="arrow-right"
@click="showDialog = false"
>
返回仪表板
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
</Dialog>
</template>
<script>
import { createResource } from 'jingrow-ui';
import { defineAsyncComponent } from 'vue';
import TextInsideCircle from './TextInsideCircle.vue';
import { toast } from 'vue-sonner';
export default {
name: 'AppTrialSubscriptionDialog',
components: {
SitePlansCards: defineAsyncComponent(() => import('./SitePlansCards.vue')),
StripeCard2: defineAsyncComponent(() =>
import('../components/StripeCard.vue')
),
UpdateBillingDetailsForm: defineAsyncComponent(() =>
import('./UpdateBillingDetailsForm.vue')
),
CardWithDetails: defineAsyncComponent(() =>
import('../../src/components/CardWithDetails.vue')
),
BuyPrepaidCreditsForm: defineAsyncComponent(() =>
import('./BuyPrepaidCreditsForm.vue')
),
AlertBanner: defineAsyncComponent(() => import('./AlertBanner.vue')),
TextInsideCircle
},
props: ['site', 'currentPlan', 'trialPlan'],
data() {
return {
step: 1,
selectedPlan: null,
billingInformation: {
cardHolderName: '',
country: '',
gstin: ''
},
isAutomatedBilling: true,
isChangingPlan: false
};
},
emits: ['update:modelValue', 'success'],
mounted() {
if (this.trialPlan && this.currentPlan !== this.trialPlan) {
this.selectedPlan = {
name: this.trialPlan
};
} else {
this.selectedPlan = null;
}
},
methods: {
confirmPlan() {
if (!this.selectedPlan) {
toast.error('请选择一个计划');
return;
}
this.step = 2;
},
onBillingAddresUpdateSuccess() {
this.$team.reload();
if (this.$team.pg.payment_mode) {
this.step = 4;
this.changePlan();
} else {
this.step = 3;
}
},
onBuyCreditsSuccess() {
this.$team.reload();
this.step = 4;
this.changePlan();
},
onAddCardSuccess() {
this.$team.reload();
this.step = 4;
this.changePlan();
},
changePlan() {
if (this.isChangingPlan) return;
this.isChangingPlan = true;
const plan_name = this.selectedPlan?.name;
let request = createResource({
url: '/api/action/jcloud.api.client.run_pg_method',
params: {
dt: 'Site',
dn: this.site,
method: 'set_plan',
args: {
plan: plan_name
}
},
onSuccess: res => {
this.isChangingPlan = false;
this.$team.reload();
this.$emit('success');
},
onError: () => {
this.isChangingPlan = false;
toast.error('更改计划失败,请稍后重试。');
this.showDialog = false;
}
});
request.submit();
}
},
computed: {
isBillingDetailsSet() {
return Boolean(this.$team.pg.billing_details?.name);
},
minimumAmount() {
return this.$team.pg.currency == 'CNY' ? 0.01 : 0.01;
},
showDialog: {
get() {
return this.modelValue;
},
set(value) {
this.$emit('update:modelValue', value);
}
}
}
};
</script>