PricingTable专业版

GitHub
一个响应式定价表格组件,显示分层定价计划及功能比较。

用法

PricingTable 组件提供了一种响应式且可定制的方式,以表格形式显示定价方案,在桌面端自动切换为水平表格布局以便比较,在移动端则切换为垂直卡片布局以提高可读性。

  • 个人版
    最受欢迎
    面向独立开发者。
    $249
    按年计费/月
    功能
    开发者数量
    1
    项目
    GitHub 仓库访问权限
    更新
    补丁和次要更新
    支持
    社区
    安全
    SSO
    审计日志
    定制安全审查
  • 团队
    面向成长型团队。
    $499
    按年计费/月
    功能
    开发者数量
    5
    项目
    GitHub 仓库访问权限
    更新
    所有更新
    支持
    优先
    安全
    SSO
    审计日志
    定制安全审查
  • 企业版
    面向大型组织。
    定制
    功能
    开发者数量
    无限制
    项目
    GitHub 仓库访问权限
    更新
    所有更新
    支持
    24/7
    安全
    SSO
    审计日志
    定制安全审查

层级

使用 tiers 属性作为对象数组来定义您的定价方案。每个层级对象支持以下属性:

  • id: string - 层级的唯一标识符(必填)
  • title?: string - 定价方案的名称
  • description?: string - 方案的简短描述
  • price?: string - 方案的当前价格(例如:“$99”、“€99”、“免费”)
  • discount?: string - 折扣价格,将以删除线显示 price(例如:“$79”、“€79”)
  • billingCycle?: string - 显示在价格旁边的单价周期(例如:“/月”、“/席位/月”)
  • billingPeriod?: string - 显示在计费周期上方的额外计费上下文(例如:“按月计费”)
  • badge?: string | BadgeProps - 在标题旁边显示徽章 { color: 'primary', variant: 'subtle' }
  • button?: ButtonProps - 配置 CTA 按钮 { size: 'lg', block: true }
  • highlight?: boolean - 是否将此层级视觉上强调为推荐选项
  • 个人版
    最受欢迎
    面向独立开发者。
    $249
    按年计费/月
  • 团队
    面向成长型团队。
    $499
    按年计费/月
  • 企业版
    面向大型组织。
    定制
<script setup lang="ts">
const tiers = ref([
  {
    id: 'solo',
    title: 'Solo',
    description: 'For indie hackers.',
    price: '$249',
    billingCycle: '/month',
    billingPeriod: 'billed annually',
    badge: 'Most popular',
    button: {
      label: 'Buy now',
      variant: 'subtle'
    }
  },
  {
    id: 'team',
    title: 'Team',
    description: 'For growing teams.',
    price: '$499',
    billingCycle: '/month',
    billingPeriod: 'billed annually',
    button: {
      label: 'Buy now'
    },
    highlight: true
  },
  {
    id: 'enterprise',
    title: 'Enterprise',
    description: 'For large organizations.',
    price: 'Custom',
    button: {
      label: 'Contact sales',
      color: 'neutral'
    }
  }
])
</script>

<template>
  <UPricingTable :tiers="tiers" />
</template>

部分

使用 sections 属性将功能组织成逻辑分组。每个部分代表您希望在不同定价层级之间进行比较的功能类别。

  • title: string - 功能部分的标题
  • features: PricingTableSectionFeature[] - 一个包含功能的数组,以及它们在每个层级中的可用性
    • 每个功能都需要一个 title 和一个将层级 ID 映射到值的 tiers 对象
    • 布尔值(true/false)将显示为勾选标记 (✓) 或减号图标 (-)
    • 字符串值将显示为文本(例如:“无限制”、“最多 5 个用户”)
    • 数字值将按原样显示(例如:10、100)
  • 个人版
    面向独立开发者。
    $249
     /月
    功能
    开发者数量
    1
    项目
    安全
    SSO
  • 团队
    面向成长型团队。
    $499
     /月
    功能
    开发者数量
    5
    项目
    安全
    SSO
  • 企业版
    面向大型组织。
    定制
    功能
    开发者数量
    无限制
    项目
    安全
    SSO
<script setup lang="ts">
const tiers = ref([
  {
    id: 'solo',
    title: 'Solo',
    price: '$249',
    description: 'For indie hackers.',
    billingCycle: '/month',
    button: {
      label: 'Buy now',
      variant: 'subtle'
    }
  },
  {
    id: 'team',
    title: 'Team',
    price: '$499',
    description: 'For growing teams.',
    billingCycle: '/month',
    button: {
      label: 'Buy now'
    }
  },
  {
    id: 'enterprise',
    title: 'Enterprise',
    price: 'Custom',
    description: 'For large organizations.',
    button: {
      label: 'Contact sales',
      color: 'neutral'
    }
  }
])
const sections = ref([
  {
    title: 'Features',
    features: [
      {
        title: 'Number of developers',
        tiers: {
          solo: '1',
          team: '5',
          enterprise: 'Unlimited'
        }
      },
      {
        title: 'Projects',
        tiers: {
          solo: true,
          team: true,
          enterprise: true
        }
      }
    ]
  },
  {
    title: 'Security',
    features: [
      {
        title: 'SSO',
        tiers: {
          solo: false,
          team: true,
          enterprise: true
        }
      }
    ]
  }
])
</script>

<template>
  <UPricingTable :tiers="tiers" :sections="sections" />
</template>

示例

使用插槽

PricingTable 组件提供了强大的插槽自定义选项,以根据您的内容定制显示。您可以使用通用插槽自定义单个元素,或使用它们的 ID 定位特定项目。

<script setup lang="ts">
const tiers = [
  {
    id: 'solo',
    title: 'Solo',
    price: '$249',
    description: 'For indie hackers.',
    billingCycle: '/month',
    button: { label: 'Buy now', variant: 'subtle' as const }
  },
  {
    id: 'team',
    title: 'Team',
    price: '$499',
    description: 'For growing teams.',
    billingCycle: '/month',
    button: { label: 'Buy now' },
    highlight: true
  },
  {
    id: 'enterprise',
    title: 'Enterprise',
    price: 'Custom',
    description: 'For large organizations.',
    button: { label: 'Contact sales', color: 'neutral' as const }
  }
]

const sections = [
  {
    id: 'features',
    title: 'Features',
    features: [
      {
        id: 'developers',
        title: 'Number of developers',
        tiers: { solo: '1', team: '5', enterprise: 'Unlimited' }
      },
      {
        id: 'projects',
        title: 'Projects',
        tiers: { solo: true, team: true, enterprise: true }
      }
    ]
  },
  {
    id: 'security',
    title: 'Security',
    features: [
      {
        title: 'SSO',
        tiers: { solo: false, team: true, enterprise: true }
      }
    ]
  }
]
</script>

<template>
  <UPricingTable :tiers="tiers" :sections="sections">
    <!-- Customize specific tier title -->
    <template #team-title="{ tier }">
      <div class="flex items-center gap-2">
        <UIcon name="i-lucide-crown" class="size-4 text-amber-500" />
        {{ tier.title }}
      </div>
    </template>

    <!-- Customize specific section title -->
    <template #section-security-title="{ section }">
      <div class="flex items-center gap-2">
        <UIcon name="i-lucide-shield-check" class="size-4 text-green-500" />
        <span class="font-semibold text-green-700">{{ section.title }}</span>
      </div>
    </template>

    <!-- Customize specific feature value -->
    <template #feature-developers-value="{ feature, tier }">
      <template v-if="feature.tiers?.[tier.id]">
        <UBadge :label="String(feature.tiers[tier.id])" color="primary" variant="soft" />
      </template>
      <UIcon v-else name="i-lucide-x" class="size-4 text-muted" />
    </template>
  </UPricingTable>
</template>

该组件支持多种插槽类型,以实现最大的自定义灵活性:

插槽类型模式描述示例
层级插槽#{tier-id}-{element}定位特定层级#team-title, #solo-price
部分插槽#section-{id|formatted-title}-title定位特定部分#section-features-title
功能插槽#feature-{id|formatted-title}-{title|value}定位特定功能#feature-developers-title
通用插槽#tier-title, #section-title适用于所有项目#feature-value
当未提供 id 时,插槽名称将从标题自动生成(例如,“Premium Features!” 变为 #section-premium-features-title)。

API

属性

属性默认值类型
as

'div'

any

此组件应渲染为的元素或组件。

tiers

PricingTableTier[]

在表格中显示的定价层级。每个层级代表一个定价方案,包含其标题、描述、价格和功能。

sections

PricingTableSection<PricingTableTier>[]

在表格中显示的功能部分。每个部分包含一个标题和功能列表,以及它们在每个层级中的可用性。

caption

string

显示在表格上方的标题。

ui

{ root?: ClassNameValue; table?: ClassNameValue; list?: ClassNameValue; item?: ClassNameValue; caption?: ClassNameValue; thead?: ClassNameValue; tbody?: ClassNameValue; tr?: ClassNameValue; th?: ClassNameValue; td?: ClassNameValue; tier?: ClassNameValue; tierTitleWrapper?: ClassNameValue; tierTitle?: ClassNameValue; tierDescription?: ClassNameValue; tierBadge?: ClassNameValue; tierPriceWrapper?: ClassNameValue; tierPrice?: ClassNameValue; tierDiscount?: ClassNameValue; tierBilling?: ClassNameValue; tierBillingPeriod?: ClassNameValue; tierBillingCycle?: ClassNameValue; tierButton?: ClassNameValue; tierFeatureIcon?: ClassNameValue; section?: ClassNameValue; sectionTitle?: ClassNameValue; feature?: ClassNameValue; featureTitle?: ClassNameValue; featureValue?: ClassNameValue; }

插槽

插槽类型
caption

{}

tier

{ tier: PricingTableTier; }

tier-title

{ tier: PricingTableTier; }

tier-description

{ tier: PricingTableTier; }

tier-badge

{ tier: PricingTableTier; }

tier-button

{ tier: PricingTableTier; }

tier-billing

{ tier: PricingTableTier; }

tier-discount

{ tier: PricingTableTier; }

tier-price

{ tier: PricingTableTier; }

section-title

{ section: PricingTableSection<PricingTableTier>; }

feature-title

{ feature: PricingTableSectionFeature<PricingTableTier>; section: PricingTableSection<PricingTableTier>; }

feature-value

{ feature: PricingTableSectionFeature<PricingTableTier>; tier: PricingTableTier; section: PricingTableSection<PricingTableTier>; }

主题

app.config.ts
export default defineAppConfig({
  uiPro: {
    pricingTable: {
      slots: {
        root: 'w-full relative',
        table: 'w-full table-fixed border-separate border-spacing-x-0 hidden md:table',
        list: 'md:hidden flex flex-col gap-6 w-full',
        item: 'p-6 flex flex-col border border-default rounded-lg',
        caption: 'sr-only',
        thead: '',
        tbody: '',
        tr: '',
        th: 'py-4 font-normal text-left border-b border-default',
        td: 'px-6 py-4 text-center border-b border-default',
        tier: 'p-6 text-left font-normal',
        tierTitleWrapper: 'flex items-center gap-3',
        tierTitle: 'text-lg font-semibold text-highlighted',
        tierDescription: 'text-sm font-normal text-muted mt-1',
        tierBadge: 'truncate',
        tierPriceWrapper: 'flex items-center gap-1 mt-4',
        tierPrice: 'text-highlighted text-3xl sm:text-4xl font-semibold',
        tierDiscount: 'text-muted line-through text-xl sm:text-2xl',
        tierBilling: 'flex flex-col justify-between min-w-0',
        tierBillingPeriod: 'text-toned truncate text-xs font-medium',
        tierBillingCycle: 'text-muted truncate text-xs font-medium',
        tierButton: 'mt-6',
        tierFeatureIcon: 'size-5 shrink-0',
        section: 'mt-6 flex flex-col gap-2',
        sectionTitle: 'font-semibold text-sm text-highlighted',
        feature: 'flex items-center justify-between gap-1',
        featureTitle: 'text-sm text-default',
        featureValue: 'text-sm text-muted flex justify-center min-w-5'
      },
      variants: {
        section: {
          true: {
            tr: '*:pt-8'
          }
        },
        active: {
          true: {
            tierFeatureIcon: 'text-primary'
          }
        },
        highlight: {
          true: {
            tier: 'bg-elevated/50 border-x border-t border-default rounded-t-lg',
            td: 'bg-elevated/50 border-x border-default',
            item: 'bg-elevated/50'
          }
        }
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      uiPro: {
        pricingTable: {
          slots: {
            root: 'w-full relative',
            table: 'w-full table-fixed border-separate border-spacing-x-0 hidden md:table',
            list: 'md:hidden flex flex-col gap-6 w-full',
            item: 'p-6 flex flex-col border border-default rounded-lg',
            caption: 'sr-only',
            thead: '',
            tbody: '',
            tr: '',
            th: 'py-4 font-normal text-left border-b border-default',
            td: 'px-6 py-4 text-center border-b border-default',
            tier: 'p-6 text-left font-normal',
            tierTitleWrapper: 'flex items-center gap-3',
            tierTitle: 'text-lg font-semibold text-highlighted',
            tierDescription: 'text-sm font-normal text-muted mt-1',
            tierBadge: 'truncate',
            tierPriceWrapper: 'flex items-center gap-1 mt-4',
            tierPrice: 'text-highlighted text-3xl sm:text-4xl font-semibold',
            tierDiscount: 'text-muted line-through text-xl sm:text-2xl',
            tierBilling: 'flex flex-col justify-between min-w-0',
            tierBillingPeriod: 'text-toned truncate text-xs font-medium',
            tierBillingCycle: 'text-muted truncate text-xs font-medium',
            tierButton: 'mt-6',
            tierFeatureIcon: 'size-5 shrink-0',
            section: 'mt-6 flex flex-col gap-2',
            sectionTitle: 'font-semibold text-sm text-highlighted',
            feature: 'flex items-center justify-between gap-1',
            featureTitle: 'text-sm text-default',
            featureValue: 'text-sm text-muted flex justify-center min-w-5'
          },
          variants: {
            section: {
              true: {
                tr: '*:pt-8'
              }
            },
            active: {
              true: {
                tierFeatureIcon: 'text-primary'
              }
            },
            highlight: {
              true: {
                tier: 'bg-elevated/50 border-x border-t border-default rounded-t-lg',
                td: 'bg-elevated/50 border-x border-default',
                item: 'bg-elevated/50'
              }
            }
          }
        }
      }
    })
  ]
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'

export default defineConfig({
  plugins: [
    vue(),
    uiPro({
      uiPro: {
        pricingTable: {
          slots: {
            root: 'w-full relative',
            table: 'w-full table-fixed border-separate border-spacing-x-0 hidden md:table',
            list: 'md:hidden flex flex-col gap-6 w-full',
            item: 'p-6 flex flex-col border border-default rounded-lg',
            caption: 'sr-only',
            thead: '',
            tbody: '',
            tr: '',
            th: 'py-4 font-normal text-left border-b border-default',
            td: 'px-6 py-4 text-center border-b border-default',
            tier: 'p-6 text-left font-normal',
            tierTitleWrapper: 'flex items-center gap-3',
            tierTitle: 'text-lg font-semibold text-highlighted',
            tierDescription: 'text-sm font-normal text-muted mt-1',
            tierBadge: 'truncate',
            tierPriceWrapper: 'flex items-center gap-1 mt-4',
            tierPrice: 'text-highlighted text-3xl sm:text-4xl font-semibold',
            tierDiscount: 'text-muted line-through text-xl sm:text-2xl',
            tierBilling: 'flex flex-col justify-between min-w-0',
            tierBillingPeriod: 'text-toned truncate text-xs font-medium',
            tierBillingCycle: 'text-muted truncate text-xs font-medium',
            tierButton: 'mt-6',
            tierFeatureIcon: 'size-5 shrink-0',
            section: 'mt-6 flex flex-col gap-2',
            sectionTitle: 'font-semibold text-sm text-highlighted',
            feature: 'flex items-center justify-between gap-1',
            featureTitle: 'text-sm text-default',
            featureValue: 'text-sm text-muted flex justify-center min-w-5'
          },
          variants: {
            section: {
              true: {
                tr: '*:pt-8'
              }
            },
            active: {
              true: {
                tierFeatureIcon: 'text-primary'
              }
            },
            highlight: {
              true: {
                tier: 'bg-elevated/50 border-x border-t border-default rounded-t-lg',
                td: 'bg-elevated/50 border-x border-default',
                item: 'bg-elevated/50'
              }
            }
          }
        }
      }
    })
  ]
})