news 2026/10/8 1:27:46

wp-calypso 计划价格展示组件 PlanPrice 深度指南:价格、折扣、货币格式化与用法详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
wp-calypso 计划价格展示组件 PlanPrice 深度指南:价格、折扣、货币格式化与用法详解
  • 前端
  • CMS

【免费下载链接】wp-calypso

The JavaScript and API powered WordPress.com

项目地址:https://gitcode.com/gh_mirrors/wp/wp-calypso
点击查看免费下载

引言

在 WordPress.com 的 Calypso 界面中,无论是套餐购买页、Jetpack 产品商店还是订阅续费入口,几乎每一个涉及"钱"的地方都需要一个统一的组件来渲染计划(Plan)的价格。PlanPrice正是这样一个开箱即用的 React 组件:它既能直接展示后端返回的预格式化价格字符串(productDisplayPrice),也能接收原始数字(rawPrice)自动完成货币符号、千分位、小数位等本地化格式化,还能通过original/discounted两个属性优雅地呈现"划线原价 + 折扣现价"的促销组合。

本文基于 wp-calypso 仓库 packages/components/src/plan-price/README.md 为核心骨架,结合组件源码、样式表、单元测试与真实业务使用场景,完整讲解该组件的全部 Props、渲染优先级、底层货币格式化原理及实战用法,帮助你在自己的页面中快速接入规范化的价格展示。

PlanPrice 是什么

PlanPrice 是一个用于展示"计划价格"的通用 React 组件,定义于 packages/components/src/plan-price/index.tsx(通过@automattic/components包对外导出)。它的核心定位是:

  • 展示一个计划的价格,可带货币符号(如$、€、kr.);
  • 支持折扣场景:当需要强调价格已打折时,官方推荐的做法是用两个<PlanPrice>并排放入一个 flexbox 容器中,一个打上original(原价,显示删除线),另一个打上discounted(折扣价,绿色高亮);
  • 支持价格区间:传入一个二元数组rawPrice即可渲染出$99.99-139.99这样的区间价格;
  • 可以用于任何需要展示计划价格的页面。

它位于@automattic/components组件库中,因此任何依赖该包的 Calypso 应用(包括client下的各个 section)都可以直接复用。

快速上手:从 README 中的两个经典用法说起

官方 README 给出了两种最典型的数据来源与用法。

用法一:直接使用后端返回的productDisplayPrice

productDisplayPrice是后端(/purchases、/plans等 REST 端点)返回的已经格式化好的价格字符串(可能还包含 HTML 标记),它通常会被存入 Redux 状态。此时组件会原样输出该字符串,不做任何货币格式化:

function MyComponent( { purchaseId } ) { const purchase = useSelector( ( state ) => getRawByPurchaseId( state, purchaseId ) ); return <PlanPrice productDisplayPrice={ purchase?.product_display_price } />; }

也可以同时传入税款信息与促销标记:

<PlanPrice productDisplayPrice={ purchase.productDisplayPrice } taxText={ purchase.taxText } isOnSale={ !! purchase.saleAmount } />;

用法二:传入rawPrice原始数字,交给组件自动格式化

当你有的是"纯数字"(例如从购买记录或续费价格函数中计算出来的数值)时,使用rawPrice并配合currencyCode,组件会调用getCurrencyObject完成货币符号、千分位、小数位等本地化处理:

<PlanPrice productDisplayPrice={ purchase.productDisplayPrice } rawPrice={ getRenewalPrice( purchase ) } currencyCode={ purchase.currencyCode } taxText={ purchase.taxText } isOnSale={ !! purchase.saleAmount } />;

注意:上面的写法中同时传入了productDisplayPrice与rawPrice,而二者的优先级规则在"渲染优先级"一节会详细说明——关键在于,只有当rawPrice是数组(价格区间)时它才会覆盖productDisplayPrice。

价格区间:rawPrice传数组

README 明确指出:如果向rawPrice传入一个包含两个数字的数组,组件将展示一个价格区间:

<PlanPrice rawPrice={ [ 132.2, 110.4 ] } original /> <PlanPrice rawPrice={ [ 99.99, 87 ] } discounted />

这是 README 中给出的完整示例。它来自 packages/components/src/plan-price/docs/example.jsx 和 index.stories.jsx 中展示的所有场景,包括:

场景代码示例
标准价格<PlanPrice rawPrice={ 99.8 } />
价格区间<PlanPrice rawPrice={ [ 99.99, 139.99 ] } />
零价格<PlanPrice rawPrice={ 0 } />
折扣价(原价+现价)flexbox 中<PlanPrice rawPrice={ 8.25 } original />+<PlanPrice rawPrice={ 2 } discounted />
折扣价格区间original用[ 8.25, 20.23 ],discounted用[ 2.25, 3 ]
带税信息追加taxText="10%"
简单视图追加isInSignup(signup 流程中的简化展示)

Props 全解析:从源码接口看 14 个可用属性

PlanPrice 的所有 Props 都定义在同文件的PlanPriceProps接口中(packages/components/src/plan-price/index.tsx#L93-L203),同时组件类在render()中给出了各属性的默认值。下表汇总了完整清单:

属性类型默认值作用说明
rawPricenumber \| [ number, number ] \| null—待格式化的原始价格;单个数字展示单价格,二元数组展示价格区间;若数组元素含 0 则不渲染;当与productDisplayPrice同时出现时,数组形式的rawPrice优先级更高
isSmallestUnitboolean—为true时,rawPrice中的数字被解释为货币最小单位(如美分)的整数,而非浮点金额;对productDisplayPrice无效
originalboolean—添加is-originalCSS 类,呈现划线原价样式
discountedboolean—添加is-discountedCSS 类,呈现绿色折扣价样式
currencyCodestring \| null'USD'货币代码(ISO 4217)。未设置或为undefined时回退到USD;显式为null时组件什么都不渲染(仅在使用rawPrice时生效)
classNamestring—追加到组件输出元素的 className 上
isOnSaleboolean—为true时在价格旁显示 "Sale" 徽章;对productDisplayPrice与displayFlatPrice无效
taxTextstring—在价格旁渲染(+X tax)文本(X为传入值);对productDisplayPrice与displayFlatPrice无效
displayPerMonthNotationboolean—在价格旁显示 "per month"(每月)标注;对productDisplayPrice与displayFlatPrice无效
productDisplayPricestring \| TranslateResult—后端预格式化的价格字符串(可含 HTML),原样输出;仅当rawPrice为数组时被忽略
omitHeadingboolean—为true时渲染<span>而非默认的<h4>标题标签
displayFlatPriceboolean—平铺展示价格:不再把整数部分与小数部分拆成两个元素,而是输出完整价格文本;开启后多数格式化选项(tax、sale、per-month、wrapper class)失效
priceDisplayWrapperClassNamestring—为每个价格渲染一个带该 class 的<div>包裹层;对displayFlatPrice与productDisplayPrice无效
isLargeCurrencyboolean—添加is-large-currency类,以大字号渲染(适用于大面额货币如韩元、印尼盾)

渲染优先级(重要)

从render()的实现(index.tsx#L36-L52)可以看出两条明确的优先级规则:

  1. productDisplayPrice优先:只要productDisplayPrice有值、且rawPrice不是长度大于 1 的数组,就直接原样输出productDisplayPrice,忽略currencyCode、taxText、isOnSale、displayPerMonthNotation等所有格式化选项;
  2. rawPrice数组覆盖:当rawPrice是数组(长度 ≥ 2)时,反过来忽略productDisplayPrice,走完整的货币格式化流程。

此外还有两个"渲染为空"的边界情况:

  • currencyCode为null且使用rawPrice→ 返回null;
  • rawPrice为二元数组且其中包含0(如[10, 0])→ 返回null,避免渲染出$10-0这种荒谬的区间。

这些规则全部有对应单元测试背书(见下文"测试验证"一节)。

底层原理:getCurrencyObject 如何完成货币本地化

当走rawPrice分支时,组件把格式化的重活委托给@automattic/number-formatters的getCurrencyObject。从 packages/format-currency/src/index.ts 的实现可以看到(注意number-formatters底层复用了该逻辑),它利用Intl.NumberFormat将金额拆解成以下结构:

  • symbol:货币符号(如$、€、kr.、C$);
  • symbolPosition:'before'或'after',决定符号在数字左侧还是右侧(例如法语加拿大fr-CA环境下,48 CAD渲染为48C$,符号在右侧);
  • integer:整数部分(字符串,已按语言习惯包含千分位分组,如44,700);
  • fraction:小数部分(含小数点分隔符,如.50);
  • hasNonZeroFraction:布尔值,标记小数部分是否非零;
  • sign:符号(如负数)。

PlanPrice 正是依据这些字段拼装出最终 DOM。以 HtmlPriceDisplay 为例:

  • 整数部分放入<span class="plan-price__integer">;
  • 小数部分放入<sup class="plan-price__fraction">,且仅当hasNonZeroFraction为真时才渲染——这也是为什么44700(DKK)会渲染成kr.44,700而不会出现多余的.00;
  • 货币符号放进<sup class="plan-price__currency-symbol">,位置由symbolPosition决定。

isSmallestUnit 的换算

isSmallestUnit意味着传入的是"最小货币单位"整数(例如美分5001表示$50.01)。测试用例 test/index.tsx#L40-L46 验证了<PlanPrice rawPrice={ 5001 } isSmallestUnit />渲染为$50.01。这对处理分、仙等无小数的整数金额非常有用,可以避免浮点运算误差。

折扣展示与样式体系

README 的核心使用建议是:要突出"价格已打折",就用两个 PlanPrice 并排放在 flexbox 容器中:

import { PlanPrice } from '@automattic/components'; export default class extends React.Component { static displayName = 'MyPlanPrice'; render() { return ( <div> <span className="my-plan-price-with-flexbox"> <PlanPrice rawPrice={ 99 } original /> <PlanPrice rawPrice={ 30 } discounted /> </span> <span className="my-plan-price-with-flexbox"> <PlanPrice rawPrice={ [ 132.2, 110.4 ] } original /> <PlanPrice rawPrice={ [ 99.99, 87 ] } discounted /> </span> </div> ); } }

上述示例完整来自 README.md,可直接复制运行。样式的核心规则在 style.scss:

  • .plan-price.is-original:使用中性浅色--color-neutral-light,并通过::before伪元素绘制一条rotate(-16deg)的 2px 删除线(--color-accent),模拟经典的"划线原价";
  • .plan-price.is-discounted:使用成功色--color-success(绿色),突出折扣后的现价;
  • .plan-price__currency-symbol/.plan-price__fraction/.plan-price__tax-amount:均以上标(vertical-align: super)呈现,字号为$font-body-extra-small;
  • .plan-price__term:每月标注的样式,0.875rem字号,左对齐。

组件还会根据omitHeading决定外层标签:默认渲染<h4>(测试 test/index.tsx#L246-L268 验证了h4与span的切换),所以在页面中它天然是一个语义化的价格标题。

单元测试验证的行为契约

packages/components/src/plan-price/test/index.tsx 使用@testing-library/react+ jsdom 环境编写了 30+ 条用例,基本锁定了组件的行为契约。以下是最值得注意的几条(均可作为你在集成时对行为的确认依据):

  • 零与区间:rawPrice={ 0 }渲染$0;rawPrice={ [10, 0] }不渲染$10-0(L22-L69);
  • 优先级:rawPrice为数字时用productDisplayPrice;rawPrice为数组时反过来覆盖productDisplayPrice;设置productDisplayPrice时currencyCode被忽略(L71-L122);
  • 货币:currencyCode="EUR"渲染€5.05;undefined回退$;null什么都不渲染;DKK下整数44700无小数位、浮点44700.5显示.50(L124-L159);
  • FlatPrice:displayFlatPrice下输出完整文本(kr.44,700.50作为一个整体文本节点),且忽略isOnSale、taxText、displayPerMonthNotation(L161-L244);
  • 符号位置:en-US下 CAD 渲染为C$48(符号在左),fr-CA下渲染为48C$(符号在右)(L354-L375);
  • CSS 类:original/discounted/isLargeCurrency分别产生is-original、is-discounted、is-large-currency类(L312-L352)。

在真实业务页面中的使用

在client侧,PlanPrice 被广泛用于套餐与产品价格展示。例如 Jetpack 产品商店的价格明细组件 client/my-sites/plans/jetpack-plans/product-store/pricing-breakdown/render-price.tsx 中:

import { PlanPrice } from '@automattic/components'; // ... <PlanPrice rawPrice={ price } currencyCode={ currencyCode } displayFlatPrice />

它选择displayFlatPrice模式,从产品数据中取出原始价格price与currencyCode,由组件统一完成本地化格式化,无需每个页面重复实现货币逻辑。类似的使用点还包括 client/my-sites/plans/jetpack-plans/product-lightbox/payment-plan.tsx、client/my-sites/plans/woo-express-plans-page/index.tsx 等。

这印证了组件的设计初衷:把"价格格式化 + 折扣视觉表现"收敛到一个组件里,业务页面只关心"给数字还是给字符串"。

常见问题与边界行为小结

  • 想要区间价格就传数组:rawPrice={[ low, high ]},中间自动渲染为{{smallerPrice}}-{{higherPrice}}(经由translate处理,支持 i18n);
  • 不要传 0 进区间数组:会被当作"无意义区间"整体不渲染;
  • productDisplayPrice可以带 HTML:组件用dangerouslySetInnerHTML原样输出,因此只应传入可信的后端数据;
  • 同时传两个 prop 时的选择:单价格时productDisplayPrice生效;区间时rawPrice生效;
  • 语义化标题:默认是<h4>,在列表/段落内嵌场景请用omitHeading降级为<span>;
  • 大面额货币:日元、韩元、印尼盾等面额很大时,用isLargeCurrency获得更小的字号(对应样式表$font-title-large与is-large-currency类的配合)。

延伸阅读

  • 组件文档与示例:packages/components/src/plan-price/README.md、docs/example.jsx、index.stories.jsx
  • 组件实现:packages/components/src/plan-price/index.tsx
  • 样式:packages/components/src/plan-price/style.scss
  • 测试:packages/components/src/plan-price/test/index.tsx
  • 底层货币格式化工具:packages/format-currency/src/index.ts 与 packages/format-currency/README.md
  • 前端
  • CMS

【免费下载链接】wp-calypso

The JavaScript and API powered WordPress.com

项目地址:https://gitcode.com/gh_mirrors/wp/wp-calypso
点击查看免费下载

相关推荐

上一篇:GitHub_Trending/py/pytudes数据库集成:SQLite与Python数据持久化
下一篇:终极指南:如何利用agno事件总线实现智能体系统的高效解耦通信

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 1:22:56

题解:洛谷 P5015 [NOIP 2018 普及组] 标题统计

本文分享的必刷题目是从蓝桥云课、洛谷、AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。 欢迎大家订阅我的专栏:算法…

作者头像 李华
网站建设 2026/10/8 1:22:56

题解:洛谷 P1827 [USACO3.4] 美国血统 American Heritage

本文分享的必刷题目是从蓝桥云课、洛谷、AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。 欢迎大家订阅我的专栏:算法…

作者头像 李华
网站建设 2026/10/8 1:22:53

题解:洛谷 P1618 三连击(升级版)

本文分享的必刷题目是从蓝桥云课、洛谷、AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。 欢迎大家订阅我的专栏:算法…

作者头像 李华