pricing.tsx
本地来源:模版/template/raphael-starterkit-v1-main/analysis/02-应用核心文件/pricing.tsx.md
Pricing组件详细分析
文件概述
components/home/pricing.tsx 是首页的定价展示组件,展示不同的订阅计划和价格信息,集成了Creem支付系统。
完整代码分析
"use client";
import { useState } from "react";
import { CheckIcon } from "@heroicons/react/20/solid";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useUser } from "@/hooks/use-user";
import { User } from "@supabase/supabase-js";
import { handleCheckout } from "@/app/actions";
导入分析:
- "use client": 标记为客户端组件,因为使用了状态和事件处理
- useState: React钩子,用于管理组件状态
- CheckIcon: Heroicons的勾选图标,用于功能列表
- Button: 自定义UI按钮组件
- cn: 类名合并工具函数
- useUser: 自定义用户状态管理钩子
- User: Supabase的用户类型
- handleCheckout: 处理结账的服务器操作
const frequencies = [
{ value: "monthly", label: "月付", priceSuffix: "/月" },
{ value: "annually", label: "年付", priceSuffix: "/年" },
];
频率配置:
- 定义订阅周期选项
- value: 内部标识符
- label: 显示标签
- priceSuffix: 价格后缀
const tiers = [
{
name: "免费版",
id: "tier-free",
href: "#",
price: { monthly: "¥0", annually: "¥0" },
description: "适合个人用户和小型项目",
features: [
"5个项目",
"1GB存储空间",
"基础分析",
"邮件支持",
],
mostPopular: false,
cta: "开始免费使用",
},
// ... 其他价格层级
];
价格层级配置:
- name: 计划名称
- id: 唯一标识符
- href: 链接地址
- price: 月付和年付价格
- description: 计划描述
- features: 功能特性列表
- mostPopular: 是否为推荐计划
- cta: 行动召唤按钮文本
export default function Pricing() {
const [frequency, setFrequency] = useState(frequencies[0]);
const { user } = useUser();
组件状态:
- frequency: 当前选择的付费频率
- setFrequency: 更新频率的函数
- user: 当前用户信息
const handlePlanSelect = async (planId: string, user: User | null) => {
if (!user) {
// 如果用户未登录,重定向到登录页面
window.location.href = "/sign-in";
return;
}
try {
// 调用服务器操作处理结账
const result = await handleCheckout(planId, user.id);
if (result.url) {
// 重定向到Creem支付页面
window.location.href = result.url;
} else {
console.error("结账失败:", result.error);
}
} catch (error) {
console.error("结账过程中出错:", error);
}
};
计划选择处理:
- 检查用户登录状态
- 未登录时重定向到登录页面
- 调用handleCheckout服务器操作
- 成功时重定向到支付页面
- 错误处理和日志记录
return (
<div className="bg-white dark:bg-gray-900 py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-4xl text-center">
<h2 className="text-base font-semibold leading-7 text-indigo-600">
定价方案
</h2>
<p className="mt-2 text-4xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-5xl">
选择适合您的计划
</p>
</div>
标题部分:
- bg-white dark:bg-gray-900: 主题背景色
- py-24 sm:py-32: 响应式垂直内边距
- mx-auto max-w-7xl px-6 lg:px-8: 居中容器,响应式水平内边距
- text-center: 文本居中对齐
- text-indigo-600: 靛蓝色小标题
- text-4xl sm:text-5xl: 响应式大标题字号
<p className="mx-auto mt-6 max-w-2xl text-center text-lg leading-8 text-gray-600 dark:text-gray-300">
我们提供灵活的定价选项,让您根据需求选择最合适的方案
</p>
描述文本:
- mx-auto max-w-2xl: 居中,限制最大宽度
- text-center text-lg leading-8: 居中对齐,大字号,行高8单位
- text-gray-600 dark:text-gray-300: 主题适配的文本颜色
<div className="mt-16 flex justify-center">
<fieldset>
<legend className="sr-only">付费频率</legend>
<div className="grid grid-cols-2 gap-x-1 rounded-full p-1 text-center text-xs font-semibold leading-5 ring-1 ring-inset ring-gray-200 dark:ring-gray-700">
{frequencies.map((option) => (
<label
key={option.value}
className={cn(
frequency.value === option.value
? "bg-indigo-600 text-white"
: "text-gray-500 dark:text-gray-400",
"cursor-pointer rounded-full px-2.5 py-1"
)}
>
<input
type="radio"
name="frequency"
value={option.value}
className="sr-only"
checked={frequency.value === option.value}
onChange={(e) => {
setFrequency(
frequencies.find((f) => f.value === e.target.value) || frequencies[0]
);
}}
/>
{option.label}
</label>
))}
</div>
</fieldset>
</div>
频率选择器:
- fieldset和legend: 语义化表单组,屏幕阅读器友好
- grid grid-cols-2 gap-x-1: 2列网格布局,水平间距1单位
- rounded-full: 圆角样式
- ring-1 ring-inset: 内边框效果
- cn(): 条件样式类名合并
- cursor-pointer: 鼠标悬停指针
- sr-only: 屏幕阅读器专用类(隐藏但可访问)
- onChange: 处理频率变更
<div className="isolate mx-auto mt-10 grid max-w-md grid-cols-1 gap-8 lg:mx-0 lg:max-w-none lg:grid-cols-3">
{tiers.map((tier) => (
<div
key={tier.id}
className={cn(
tier.mostPopular
? "ring-2 ring-indigo-600"
: "ring-1 ring-gray-200 dark:ring-gray-700",
"rounded-3xl p-8 xl:p-10"
)}
>
价格卡片网格:
- isolate: 创建层叠上下文
- grid max-w-md grid-cols-1: 小屏幕单列网格
- lg:grid-cols-3: 大屏幕三列网格
- gap-8: 网格间距8单位
- 条件样式:推荐计划使用特殊边框
<div className="flex items-center justify-between gap-x-4">
<h3
id={tier.id}
className="text-lg font-semibold leading-8 text-gray-900 dark:text-white"
>
{tier.name}
</h3>
{tier.mostPopular ? (
<p className="rounded-full bg-indigo-600/10 px-2.5 py-1 text-xs font-semibold leading-5 text-indigo-600">
最受欢迎
</p>
) : null}
</div>
计划标题:
- flex items-center justify-between: 两端对齐布局
- gap-x-4: 水平间距4单位
- id={tier.id}: 为无障碍访问设置id
- 条件渲染"最受欢迎"标签
<p className="mt-6 text-sm leading-6 text-gray-600 dark:text-gray-300">
{tier.description}
</p>
<p className="mt-6 flex items-baseline gap-x-1">
<span className="text-4xl font-bold tracking-tight text-gray-900 dark:text-white">
{tier.price[frequency.value as keyof typeof tier.price]}
</span>
<span className="text-sm font-semibold leading-6 text-gray-600 dark:text-gray-300">
{frequency.priceSuffix}
</span>
</p>
计划描述和价格:
- mt-6: 顶部外边距6单位
- text-sm leading-6: 小字号,行高6单位
- flex items-baseline gap-x-1: 基线对齐,间距1单位
- text-4xl font-bold tracking-tight: 大字号,粗体,紧密间距
- 动态价格显示基于选择的频率
<Button
onClick={() => handlePlanSelect(tier.id, user)}
className="mt-6 w-full"
variant={tier.mostPopular ? "default" : "outline"}
>
{tier.cta}
</Button>
行动召唤按钮:
- onClick: 处理计划选择
- w-full: 全宽按钮
- variant: 推荐计划使用默认样式,其他使用轮廓样式
- 动态按钮文本
<ul className="mt-8 space-y-3 text-sm leading-6 text-gray-600 dark:text-gray-300">
{tier.features.map((feature) => (
<li key={feature} className="flex gap-x-3">
<CheckIcon
className="h-6 w-5 flex-none text-indigo-600"
aria-hidden="true"
/>
{feature}
</li>
))}
</ul>
功能特性列表:
- mt-8 space-y-3: 顶部外边距8单位,垂直间距3单位
- text-sm leading-6: 小字号,行高6单位
- flex gap-x-3: 水平布局,间距3单位
- CheckIcon: 勾选图标,表示功能包含
- h-6 w-5 flex-none: 图标尺寸,不收缩
- aria-hidden="true": 对屏幕阅读器隐藏装饰性图标
</div>
))}
</div>
</div>
</div>
);
}
设计模式分析
1. 配置驱动模式
- 使用
frequencies和tiers数组配置界面 - 易于维护和扩展
- 数据与视图分离
2. 状态管理模式
- 使用
useState管理选择状态 - 响应式UI更新
- 用户交互状态同步
3. 条件渲染模式
- 基于状态的动态UI
- 主题适配和特殊标识
- 用户状态检查
4. 错误处理模式
- Try-catch错误捕获
- 用户友好的错误处理
- 日志记录用于调试
技术亮点
1. 支付集成
- 无缝集成Creem支付系统
- 服务器端结账处理
- 安全的支付流程
2. 响应式设计
- 移动优先的布局策略
- 灵活的网格系统
- 优雅的断点处理
3. 无障碍访问
- 语义化HTML结构
- 屏幕阅读器友好
- 键盘导航支持
4. 主题支持
- 深色/浅色模式
- 动态颜色系统
- 一致的视觉风格
使用示例
// 在页面中使用Pricing组件
import Pricing from '@/components/home/pricing';
export default function PricingPage() {
return (
<div>
<Pricing />
</div>
);
}
最佳实践
- 配置驱动: 使用配置对象而不是硬编码
- 错误处理: 完整的错误处理和用户反馈
- 无障碍性: 语义化标记和屏幕阅读器支持
- 性能优化: 条件渲染和状态优化
- 安全性: 服务器端验证和处理
这个Pricing组件展示了如何构建一个功能完整的定价页面,包括支付集成、响应式设计、主题支持和无障碍访问。
本文档为站内渲染。原始文件本地路径:saas/source/templates/模版-template-raphael-starterkit-v1-main-analysis-02-应用核心文件-pr-02e85b.md(仅本地保留,不入库不部署)