知识库首页 模版 pricing.tsx.md

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>

频率选择器: - fieldsetlegend: 语义化表单组,屏幕阅读器友好 - 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. 配置驱动模式

  • 使用frequenciestiers数组配置界面
  • 易于维护和扩展
  • 数据与视图分离

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>
  );
}

最佳实践

  1. 配置驱动: 使用配置对象而不是硬编码
  2. 错误处理: 完整的错误处理和用户反馈
  3. 无障碍性: 语义化标记和屏幕阅读器支持
  4. 性能优化: 条件渲染和状态优化
  5. 安全性: 服务器端验证和处理

这个Pricing组件展示了如何构建一个功能完整的定价页面,包括支付集成、响应式设计、主题支持和无障碍访问。

本文档为站内渲染。原始文件本地路径:saas/source/templates/模版-template-raphael-starterkit-v1-main-analysis-02-应用核心文件-pr-02e85b.md(仅本地保留,不入库不部署)