179 lines
5.8 KiB
Plaintext
179 lines
5.8 KiB
Plaintext
<template>
|
|
<view class="plan-cards">
|
|
<view class="section-title-container">
|
|
<view class="section-title-bar"></view>
|
|
<text class="section-title">{{
|
|
t("Mobile.MySubscriptions.planGrid")
|
|
}}</text>
|
|
</view>
|
|
|
|
<view class="cards-list">
|
|
<view
|
|
v-for="plan in plans"
|
|
:key="plan.id"
|
|
class="plan-card"
|
|
:class="{ 'plan-current': plan.id === currentPlanId }"
|
|
>
|
|
<!-- 标签 -->
|
|
<view class="plan-badges">
|
|
<view v-if="plan.id === currentPlanId" class="badge badge-active">
|
|
<text class="badge-text">{{
|
|
t("Mobile.MySubscriptions.currentPlan")
|
|
}}</text>
|
|
</view>
|
|
<view v-else-if="plan.isHot" class="badge badge-hot">
|
|
<text class="badge-text">{{
|
|
t("Mobile.MySubscriptions.statusHot")
|
|
}}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 主体内容 -->
|
|
<view class="plan-card-body">
|
|
<!-- 套餐信息 -->
|
|
<text class="plan-name">{{ plan.name }}</text>
|
|
<text v-if="plan.description" class="plan-desc">{{
|
|
plan.description
|
|
}}</text>
|
|
<view class="plan-price-row">
|
|
<text class="plan-price-symbol">¥</text>
|
|
<text class="plan-price-value">{{
|
|
formatNumber(plan.price, 2)
|
|
}}</text>
|
|
<text class="plan-period">{{ getPeriodUnit(plan.period) }}</text>
|
|
</view>
|
|
<view class="plan-credits-tag">
|
|
<text class="plan-credits-text">
|
|
{{
|
|
`${formatNumber(plan.creditAmount, 0)} ${t("Mobile.MySubscriptions.creditUnit")}${t("Mobile.MySubscriptions.perMonth")}`
|
|
}}
|
|
</text>
|
|
</view>
|
|
|
|
<!-- 按钮(移入副标题及价格信息下方,与 PC 端保持一致) -->
|
|
<view class="plan-action-area">
|
|
<view
|
|
class="plan-btn"
|
|
:class="{
|
|
'plan-btn-processing': processingId === plan.id,
|
|
'plan-btn-current': plan.id === currentPlanId,
|
|
'plan-btn-disabled':
|
|
plan.price <= 0 && plan.id !== currentPlanId,
|
|
}"
|
|
@tap="handleAction(plan)"
|
|
>
|
|
<text class="plan-btn-text">{{ getButtonText(plan) }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 分割线(与 PC 端保持一致) -->
|
|
<view class="plan-divider"></view>
|
|
|
|
<!-- 权益列表 -->
|
|
<view class="benefits" v-if="getBaseBenefits(plan).length > 0">
|
|
<view class="benefit-group">
|
|
<view
|
|
v-for="(item, index) in getBaseBenefits(plan)"
|
|
:key="index"
|
|
class="benefit-item"
|
|
>
|
|
<view
|
|
class="benefit-icon-container"
|
|
:class="item.selected ? 'icon-check' : 'icon-cross'"
|
|
>
|
|
<text class="benefit-icon-text">{{
|
|
item.selected ? "✓" : "✗"
|
|
}}</text>
|
|
</view>
|
|
<text class="benefit-text">{{ item.description }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="uts">
|
|
import type {
|
|
SystemSubscriptionPlan,
|
|
SystemSubscriptionPlanGroup,
|
|
SystemSubscriptionPlanItem,
|
|
} from "@/subpackages/types/interfaces/subscription";
|
|
import { useI18n } from "@/utils/i18n";
|
|
import { getPeriodUnit } from "../../utils.uts";
|
|
import { formatDate, formatNumber } from "@/utils/system";
|
|
import { useSubscriptionPurchase } from "../../hooks/useSubscriptionPurchase";
|
|
|
|
const { t } = useI18n();
|
|
const { processingId, handlePaySubscription } = useSubscriptionPurchase();
|
|
|
|
interface Props {
|
|
plans: SystemSubscriptionPlan[];
|
|
currentPlanId: number | null;
|
|
currentEndTime: string | null;
|
|
currentPlanPrice?: number | null;
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
currentPlanPrice: null,
|
|
});
|
|
|
|
const getActionVerb = (planPrice: number): string => {
|
|
if (props.currentPlanId == null) {
|
|
return t("Mobile.MySubscriptions.subscribeNow");
|
|
}
|
|
const currentPrice = props.currentPlanPrice ?? 0;
|
|
if (planPrice <= currentPrice) {
|
|
return t("Mobile.MySubscriptions.subscribeNow");
|
|
}
|
|
return t("Mobile.MySubscriptions.upgradeNow");
|
|
};
|
|
|
|
const getButtonText = (plan: SystemSubscriptionPlan): string => {
|
|
if (plan.id === props.currentPlanId) {
|
|
const currentPlanBtnText = t("Mobile.MySubscriptions.currentPlanButton");
|
|
const renewNowText = t("Mobile.MySubscriptions.renewNow");
|
|
return `${currentPlanBtnText}(${renewNowText})`;
|
|
}
|
|
|
|
const verbText = getActionVerb(plan.price);
|
|
let periodText = t("Mobile.MySubscriptions.continuousMonthly");
|
|
if (plan.period === 3) {
|
|
periodText = t("Mobile.MySubscriptions.continuousQuarterly");
|
|
} else if (plan.period === 12) {
|
|
periodText = t("Mobile.MySubscriptions.continuousYearly");
|
|
}
|
|
|
|
return `${verbText}${plan.name}${periodText}`;
|
|
};
|
|
|
|
const handleAction = async (plan: SystemSubscriptionPlan) => {
|
|
if (plan.price <= 0 && plan.id !== props.currentPlanId) return;
|
|
await handlePaySubscription(plan.id);
|
|
};
|
|
|
|
const getBaseBenefits = (
|
|
plan: SystemSubscriptionPlan,
|
|
): SystemSubscriptionPlanItem[] => {
|
|
const list = [] as SystemSubscriptionPlanItem[];
|
|
const groups = plan.itemGroups;
|
|
if (groups != null) {
|
|
for (let i = 0; i < groups.length; i++) {
|
|
const g = groups[i];
|
|
if (g.groupType === "BASE" && g.items != null) {
|
|
for (let j = 0; j < g.items.length; j++) {
|
|
list.push(g.items[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return list;
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@import "./plan-cards.scss";
|
|
</style>
|