From 46891596d866e97b52de760518bb4192770c15a7 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Mon, 27 Apr 2026 10:35:36 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(Price):=20=E7=BB=84=E4=BB=B6=E4=BC=A0?= =?UTF-8?q?=E5=85=A5=E5=BC=82=E5=B8=B8=E4=BF=A1=E6=81=AF=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E5=8E=9F=E6=A0=B7=E8=BF=94=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/packages/price/price.taro.tsx | 17 ++++++++++++++--- src/utils/should-render-price-raw.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 src/utils/should-render-price-raw.ts diff --git a/src/packages/price/price.taro.tsx b/src/packages/price/price.taro.tsx index f491494e08..2c9b3150e0 100644 --- a/src/packages/price/price.taro.tsx +++ b/src/packages/price/price.taro.tsx @@ -5,6 +5,7 @@ import { ComponentDefaults } from '@/utils/typings' import { useRtl } from '@/packages/configprovider/index.taro' import { TaroPriceProps, PriceColorEnum } from '@/types' import { harmony } from '@/utils/taro/platform' +import { shouldRenderPriceAsRaw } from '@/utils/should-render-price-raw' const defaultProps = { ...ComponentDefaults, @@ -38,9 +39,17 @@ export const Price: FunctionComponent> = (props) => { const rtl = useRtl() + const isRenderPriceRaw = useMemo( + () => + typeof originalPrice === 'string' && + shouldRenderPriceAsRaw(originalPrice), + [originalPrice] + ) + const price = useMemo(() => { + if (isRenderPriceRaw) return '' return originalPrice.toString().replace(/[^\d.]/g, '') - }, [originalPrice]) + }, [originalPrice, isRenderPriceRaw]) const isCustomPriceColor = useMemo(() => { const specificPriceColor = Object.values(PriceColorEnum) @@ -164,6 +173,8 @@ export const Price: FunctionComponent> = (props) => { ) } + const renderRawContent = () => <>{originalPrice} + return ( <> {harmony() || process.env.TARO_ENV === 'dynamic' ? ( @@ -171,14 +182,14 @@ export const Price: FunctionComponent> = (props) => { className={`${classPrefix} ${classPrefix}-${color} ${className}`} style={style} > - {renderInner()} + {isRenderPriceRaw ? renderRawContent() : renderInner()} ) : ( - {renderInner()} + {isRenderPriceRaw ? renderRawContent() : renderInner()} )} diff --git a/src/utils/should-render-price-raw.ts b/src/utils/should-render-price-raw.ts new file mode 100644 index 0000000000..7a8d43ea60 --- /dev/null +++ b/src/utils/should-render-price-raw.ts @@ -0,0 +1,26 @@ +const CJK = /[\u4E00-\u9FFF]/ +const RE_NUM = /\d+(?:\.\d+)?/g + +function hasNoExtractablePrice(s: string) { + const t = s.trim() + if (!t) return true + if (!/\d/.test(t)) return true + if (t.replace(/[^\d.]/g, '') === '') return true + return false +} + +export function shouldRenderPriceAsRaw(s: string) { + if (hasNoExtractablePrice(s)) { + return true + } + const t = s.trim() + if (!CJK.test(t)) return false + const matches = Array.from(t.matchAll(RE_NUM)) + if (matches.length < 2) return false + const a = matches[0] + const b = matches[1] + const i0 = a.index! + const i1 = b.index! + const between = t.slice(i0 + a[0].length, i1) + return CJK.test(between) +} From 6022c3a5c074f321f8e1df6f881e216ceae95299 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Mon, 27 Apr 2026 14:57:55 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(Price):=20=E6=94=AF=E6=8C=81=E6=97=A0?= =?UTF-8?q?=E9=9A=9C=E7=A2=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/packages/price/price.taro.tsx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/packages/price/price.taro.tsx b/src/packages/price/price.taro.tsx index 2c9b3150e0..8bd1f4c771 100644 --- a/src/packages/price/price.taro.tsx +++ b/src/packages/price/price.taro.tsx @@ -128,6 +128,8 @@ export const Price: FunctionComponent> = (props) => { }, ])} style={priceColorStyle} + // @ts-ignore + ariaHidden > {symbol ? replaceSpecialChar(symbol) : ''} @@ -142,6 +144,8 @@ export const Price: FunctionComponent> = (props) => { line ? `${classPrefix}-line` : '' }`} style={priceColorStyle} + // @ts-ignore + ariaHidden > {formatThousands(price)} @@ -153,6 +157,8 @@ export const Price: FunctionComponent> = (props) => { line ? `${classPrefix}-line` : '' }`} style={priceColorStyle} + // @ts-ignore + ariaHidden > . @@ -162,6 +168,8 @@ export const Price: FunctionComponent> = (props) => { line ? `${classPrefix}-line` : '' }`} style={priceColorStyle} + // @ts-ignore + ariaHidden > {formatDecimal(price)} @@ -174,6 +182,13 @@ export const Price: FunctionComponent> = (props) => { } const renderRawContent = () => <>{originalPrice} + const accessiblePriceText = useMemo(() => { + const safeSymbol = symbol ? replaceSpecialChar(symbol) : '' + const numberText = isRenderPriceRaw ? `${originalPrice}` : `${price}` + return position === 'after' + ? `${numberText}${safeSymbol}` + : `${safeSymbol}${numberText}` + }, [symbol, price, position, originalPrice, isRenderPriceRaw]) return ( <> @@ -181,6 +196,10 @@ export const Price: FunctionComponent> = (props) => { {isRenderPriceRaw ? renderRawContent() : renderInner()} @@ -188,6 +207,9 @@ export const Price: FunctionComponent> = (props) => { {isRenderPriceRaw ? renderRawContent() : renderInner()} From 7d172f58e179ad3fa5697940fa1d0ba097c26d90 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 7 May 2026 09:29:20 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat(Price):=20=E5=A2=9E=E5=8A=A0raw,=20?= =?UTF-8?q?=E5=BC=80=E5=90=AF=E5=90=8Eprice=20=E5=8C=85=E5=90=AB=E9=9D=9E?= =?UTF-8?q?=E6=B3=95=E5=AD=97=E7=AC=A6=E6=97=B6=E5=8E=9F=E6=A0=B7=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/packages/price/doc.en-US.md | 1 + src/packages/price/doc.md | 1 + src/packages/price/doc.taro.md | 1 + src/packages/price/doc.zh-TW.md | 1 + src/packages/price/price.taro.tsx | 18 +++++++++++++----- src/packages/price/price.tsx | 18 +++++++++++++----- src/types/spec/price/base.ts | 1 + src/utils/should-render-price-raw.ts | 14 +++++++++++--- 8 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/packages/price/doc.en-US.md b/src/packages/price/doc.en-US.md index 62981b1732..9cf606d2cd 100644 --- a/src/packages/price/doc.en-US.md +++ b/src/packages/price/doc.en-US.md @@ -112,6 +112,7 @@ import { Price } from '@nutui/nutui-react' | position | The symbol appear before or after the price,`before`、`after` | `string` | `before` | | size | Size,`xlarge` \| `large` \| `normal` \| `small` | `string` | `normal` | | line | Line-through price | `boolean` | `false` | +| raw | When enabled, if price contains invalid characters, it is displayed as-is without formatting | `boolean` | `false` | ## Theming diff --git a/src/packages/price/doc.md b/src/packages/price/doc.md index 56daa10866..c19d3e0cf7 100644 --- a/src/packages/price/doc.md +++ b/src/packages/price/doc.md @@ -112,6 +112,7 @@ import { Price } from '@nutui/nutui-react' | position | 符号显示在价格前或者后,`before`、`after` | `string` | `before` | | size | 价格尺寸,`xlarge` \| `large` \| `normal` \| `small` | `string` | `normal` | | line | 是否划线价 | `boolean` | `false` | +| raw | 开启后,当 price 包含非法字符时,原样展示,不做格式化处理 | `boolean` | `false` | ## 主题定制 diff --git a/src/packages/price/doc.taro.md b/src/packages/price/doc.taro.md index a61fea1bbd..e2927667c0 100644 --- a/src/packages/price/doc.taro.md +++ b/src/packages/price/doc.taro.md @@ -112,6 +112,7 @@ import { Price } from '@nutui/nutui-react-taro' | position | 符号显示在价格前或者后,`before`、`after` | `string` | `before` | | size | 价格尺寸,`xlarge` \| `large` \| `normal` \| `small` | `string` | `normal` | | line | 是否划线价 | `boolean` | `false` | +| raw | 开启后,当 price 包含非法字符时,原样展示,不做格式化处理 | `boolean` | `false` | ## 主题定制 diff --git a/src/packages/price/doc.zh-TW.md b/src/packages/price/doc.zh-TW.md index 3b48215736..58da31f8ff 100644 --- a/src/packages/price/doc.zh-TW.md +++ b/src/packages/price/doc.zh-TW.md @@ -112,6 +112,7 @@ import { Price } from '@nutui/nutui-react' | position | 符號顯示在價格前或者後,`before`、`after` | `string` | `before` | | size | 價格尺寸,`xlarge` \| `large` \| `normal` \| `small` | `string` | `normal` | | line | 是否劃線價 | `boolean` | `false` | +| raw | 開啟後,當 price 包含非法字符時,原樣展示,不做格式化處理 | `boolean` | `false` | ## 主題定製 diff --git a/src/packages/price/price.taro.tsx b/src/packages/price/price.taro.tsx index 8bd1f4c771..3f5f15e048 100644 --- a/src/packages/price/price.taro.tsx +++ b/src/packages/price/price.taro.tsx @@ -5,7 +5,10 @@ import { ComponentDefaults } from '@/utils/typings' import { useRtl } from '@/packages/configprovider/index.taro' import { TaroPriceProps, PriceColorEnum } from '@/types' import { harmony } from '@/utils/taro/platform' -import { shouldRenderPriceAsRaw } from '@/utils/should-render-price-raw' +import { + shouldRenderPriceAsRaw, + hasNonPriceChars, +} from '@/utils/should-render-price-raw' const defaultProps = { ...ComponentDefaults, @@ -17,6 +20,7 @@ const defaultProps = { position: 'before', size: 'normal', line: false, + raw: false, } as TaroPriceProps export const Price: FunctionComponent> = (props) => { const { @@ -28,6 +32,7 @@ export const Price: FunctionComponent> = (props) => { position, size, line, + raw, className, style, } = { @@ -41,9 +46,12 @@ export const Price: FunctionComponent> = (props) => { const isRenderPriceRaw = useMemo( () => - typeof originalPrice === 'string' && - shouldRenderPriceAsRaw(originalPrice), - [originalPrice] + (typeof originalPrice === 'string' && + shouldRenderPriceAsRaw(originalPrice)) || + (raw && + typeof originalPrice === 'string' && + hasNonPriceChars(originalPrice)), + [originalPrice, raw] ) const price = useMemo(() => { @@ -95,7 +103,7 @@ export const Price: FunctionComponent> = (props) => { } const formatDecimal = (decimalNum: any) => { - if (Number(decimalNum) === 0) { + if (Number(decimalNum) === 0 && !checkPoint(decimalNum)) { decimalNum = 0 } diff --git a/src/packages/price/price.tsx b/src/packages/price/price.tsx index 44f3c2474d..38d9ceb54f 100644 --- a/src/packages/price/price.tsx +++ b/src/packages/price/price.tsx @@ -3,7 +3,10 @@ import classNames from 'classnames' import { ComponentDefaults } from '@/utils/typings' import { useRtl } from '@/packages/configprovider/index' import { WebPriceProps, PriceColorEnum } from '@/types' -import { shouldRenderPriceAsRaw } from '@/utils/should-render-price-raw' +import { + shouldRenderPriceAsRaw, + hasNonPriceChars, +} from '@/utils/should-render-price-raw' const defaultProps = { ...ComponentDefaults, @@ -15,6 +18,7 @@ const defaultProps = { position: 'before', size: 'normal', line: false, + raw: false, } as WebPriceProps export const Price: FunctionComponent> = (props) => { const { @@ -26,6 +30,7 @@ export const Price: FunctionComponent> = (props) => { position, size, line, + raw, className, style, ...rest @@ -39,9 +44,12 @@ export const Price: FunctionComponent> = (props) => { const rtl = useRtl() const isRenderPriceRaw = useMemo( () => - typeof originalPrice === 'string' && - shouldRenderPriceAsRaw(originalPrice), - [originalPrice] + (typeof originalPrice === 'string' && + shouldRenderPriceAsRaw(originalPrice)) || + (raw && + typeof originalPrice === 'string' && + hasNonPriceChars(originalPrice)), + [originalPrice, raw] ) const price = useMemo(() => { @@ -82,7 +90,7 @@ export const Price: FunctionComponent> = (props) => { } const formatDecimal = (decimalNum: any) => { - if (Number(decimalNum) === 0) { + if (Number(decimalNum) === 0 && !checkPoint(decimalNum)) { decimalNum = 0 } diff --git a/src/types/spec/price/base.ts b/src/types/spec/price/base.ts index 82d67de4b3..74a21693e4 100644 --- a/src/types/spec/price/base.ts +++ b/src/types/spec/price/base.ts @@ -19,4 +19,5 @@ export interface BasePrice extends BaseProps { position: string size: PriceSize line: boolean + raw: boolean } diff --git a/src/utils/should-render-price-raw.ts b/src/utils/should-render-price-raw.ts index 7a8d43ea60..d07c4f4728 100644 --- a/src/utils/should-render-price-raw.ts +++ b/src/utils/should-render-price-raw.ts @@ -1,6 +1,9 @@ -const CJK = /[\u4E00-\u9FFF]/ const RE_NUM = /\d+(?:\.\d+)?/g +// 价格合法字符:数字、小数点、负号、大写货币代码(HK USD EUR 等)、常见货币符号 Unicode 区块(含全角 ¥) +const PRICE_CHARS = /^[\d.\-A-Z¥¥$€£₩₹₽₺₴₪฿\u20A0-\u20CF\uFE69\uFF04\uFFE5]*$/ + +// 无法提取出有效价格时直接按原文展示,避免把纯文案误格式化成 0 或空串 function hasNoExtractablePrice(s: string) { const t = s.trim() if (!t) return true @@ -9,12 +12,12 @@ function hasNoExtractablePrice(s: string) { return false } +// 只有两段数字之间出现非法字符时,才认为该字符串应走原样渲染 export function shouldRenderPriceAsRaw(s: string) { if (hasNoExtractablePrice(s)) { return true } const t = s.trim() - if (!CJK.test(t)) return false const matches = Array.from(t.matchAll(RE_NUM)) if (matches.length < 2) return false const a = matches[0] @@ -22,5 +25,10 @@ export function shouldRenderPriceAsRaw(s: string) { const i0 = a.index! const i1 = b.index! const between = t.slice(i0 + a[0].length, i1) - return CJK.test(between) + return !PRICE_CHARS.test(between) +} + +// 提供给 Price.raw 开关:调用方可用它判断整串是否包含非法字符 +export function hasNonPriceChars(s: string): boolean { + return !PRICE_CHARS.test(s) }