Skip to content

Commit

Permalink
fix(form): 金额格式化支持负数形式展示 (ant-design#6080)
Browse files Browse the repository at this point in the history
* fix: 金额格式化支持负数形式展示

* feat: 删除无用引入

* 取消限制

* add docs

* add docs

* add docs

* add docs

Co-authored-by: tangwenhui <tangwenhui@rd.netease.com>
Co-authored-by: 期贤 <qixian.cs@antgroup.com>
  • Loading branch information
3 people committed Oct 17, 2022
1 parent 7a38415 commit b0b153b
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 31 deletions.
10 changes: 10 additions & 0 deletions packages/descriptions/src/demos/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default () => {
id: '这是一段文本columns',
date: '20200809',
money: '1212100',
money2: -12345.33,
state: 'all',
switch: true,
state2: 'open',
Expand Down Expand Up @@ -84,6 +85,15 @@ export default () => {
moneySymbol: false,
},
},
{
title: 'money负数无符号',
key: 'money2',
dataIndex: 'money2',
valueType: 'money',
fieldProps: {
moneySymbol: false,
},
},
{
title: '操作',
valueType: 'option',
Expand Down
143 changes: 121 additions & 22 deletions packages/field/src/components/Money/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ export type FieldMoneyProps = {
text: number;
moneySymbol?: boolean;
locale?: string;
/**
* 输入框内容为空的提示内容
*/
placeholder?: any;
/**
* 自定义 money 的 Symbol
*/
customSymbol?: string;
/** 自定义 Popover 的值,false 可以关闭他 */
/**
* 自定义 Popover 的值,false 可以关闭他
*/
numberPopoverRender?:
| ((props: InputNumberProps, defaultText: string) => React.ReactNode)
| boolean;
Expand All @@ -29,16 +37,50 @@ export type FieldMoneyProps = {
* @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
*/
numberFormatOptions?: {
/**
* @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
*/
localeMatcher?: string;
/**
* @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
*/
style?: string;
/**
* @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
*/
currency?: string;
/**
* @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
*/
currencyDisplay?: string;
/**
* @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
*/
currencySign?: string;
/**
* @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
*/
useGrouping?: boolean;
/**
* @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
*/
minimumIntegerDigits?: number;
/**
* @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
*/
minimumFractionDigits?: number;
/**
* @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
*/
maximumFractionDigits?: number;
/**
* @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
*/
minimumSignificantDigits?: number;

/**
* @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
*/
maximumSignificantDigits?: number;
};
};
Expand Down Expand Up @@ -86,6 +128,16 @@ const intlMap = {
'pt-BR': ptMoneyIntl,
};

/**
* A function that formats the number.
* @param {string | false} moneySymbol - The currency symbol, which is the first parameter of the
* formatMoney function.
* @param {number | string | undefined} paramsText - The text to be formatted
* @param {number} precision - number, // decimal places
* @param {any} [config] - the configuration of the number format, which is the same as the
* configuration of the number format in the Intl.NumberFormat method.
* @returns A function that takes in 4 parameters and returns a string.
*/
const getTextByLocale = (
moneySymbol: string | false,
paramsText: number | string | undefined,
Expand All @@ -98,25 +150,47 @@ const getTextByLocale = (
}

if (!moneyText && moneyText !== 0) return '';

try {
// readonly moneySymbol = false, unused currency
return (
new Intl.NumberFormat(moneySymbol || 'zh-Hans-CN', {
...(intlMap[moneySymbol || 'zh-Hans-CN'] || intlMap['zh-Hans-CN']),
maximumFractionDigits: precision,
...config,
})
// fix: #6003 解决未指定货币符号时,金额文本格式化异常问题
.format(moneyText)
.substring(+(moneySymbol === false))
);
// Formatting the number, when readonly moneySymbol = false, unused currency.
const finalMoneyText = new Intl.NumberFormat(moneySymbol || 'zh-Hans-CN', {
...(intlMap[moneySymbol || 'zh-Hans-CN'] || intlMap['zh-Hans-CN']),
maximumFractionDigits: precision,
...config,
})
// fix: #6003 解决未指定货币符号时,金额文本格式化异常问题
.format(moneyText);

// 是否有金额符号,例如 ¥ $
const hasMoneySymbol = moneySymbol === false;

/**
* 首字母判断是否是正负符号
*/
const [operatorSymbol] = finalMoneyText || '';

// 兼容正负号
if (['+', '-'].includes(operatorSymbol)) {
// 裁剪字符串,有符号截取两位,没有符号截取一位
return `${operatorSymbol}${finalMoneyText.substring(hasMoneySymbol ? 2 : 1)}`;
}

// 没有正负符号截取一位
return finalMoneyText.substring(hasMoneySymbol ? 1 : 0);
} catch (error) {
return moneyText;
}
};

// 默认的代码类型
const DefaultPrecisionCont = 2;

/**
* input 的弹框,用于显示格式化之后的内容
*
* @result 10,000 -> 一万
* @result 10, 00, 000, 000 -> 一亿
*/
const InputNumberPopover = React.forwardRef<
any,
InputNumberProps & {
Expand All @@ -131,6 +205,10 @@ const InputNumberPopover = React.forwardRef<
value: rest.value,
onChange: rest.onChange,
});

/**
* 如果content 存在要根据 content 渲染一下
*/
const dom = content?.({
...rest,
value,
Expand Down Expand Up @@ -192,37 +270,58 @@ const FieldMoney: ProFieldFC<FieldMoneyProps> = (
if (locale && allIntlMap[locale]) {
intl = allIntlMap[locale];
}
const moneySymbol = useMemo(() => {

/**
* 获取货币的符号
* 如果 customSymbol 存在直接使用 customSymbol
* 如果 moneySymbol 为 false,返回空
* 如果没有配置使用默认的
*/
const moneySymbol = useMemo((): string | undefined => {
if (customSymbol) {
return customSymbol;
}
const defaultText = intl.getMessage('moneySymbol', '¥');

if (rest.moneySymbol === false || fieldProps.moneySymbol === false) {
return undefined;
}
return defaultText;
return intl.getMessage('moneySymbol', '¥');
}, [customSymbol, fieldProps.moneySymbol, intl, rest.moneySymbol]);

/*
* A function that formats the number.
* 1000 -> 1,000
*/
const getFormateValue = useCallback(
(value?: string | number) => {
// 新建数字正则,需要配置小数点
const reg = new RegExp(
`\\B(?=(\\d{${3 + Math.max(precision - DefaultPrecisionCont, 0)}})+(?!\\d))`,
'g',
);
const [intS, floatS] = String(value).split('.');
const resInt = intS.replace(reg, ',');
let resFloat = '';
if (floatS && precision > 0)
resFloat = `.${floatS.slice(
// 切分为 整数 和 小数 不同
const [intStr, floatStr] = String(value).split('.');

// 最终的数据string,需要去掉 , 号。
const resultInt = intStr.replace(reg, ',');

// 计算最终的小数点
let resultFloat = '';

/* Taking the floatStr and slicing it to the precision. */
if (floatStr && precision > 0) {
resultFloat = `.${floatStr.slice(
0,
precision === undefined ? DefaultPrecisionCont : precision,
)}`;
}

return `${resInt}${resFloat}`;
return `${resultInt}${resultFloat}`;
},
[precision],
);

// 如果是阅读模式,直接返回字符串
if (type === 'read') {
const dom = (
<span ref={ref}>
Expand All @@ -246,7 +345,6 @@ const FieldMoney: ProFieldFC<FieldMoneyProps> = (
content={(props) => {
if (numberPopoverRender === false) return;
if (!props.value) return;

const localeText = getTextByLocale(
moneySymbol ? locale : false,
`${getFormateValue(props.value)}`,
Expand Down Expand Up @@ -289,6 +387,7 @@ const FieldMoney: ProFieldFC<FieldMoneyProps> = (
])}
/>
);

if (renderFormItem) {
return renderFormItem(text, { mode: type, ...fieldProps }, dom);
}
Expand Down
1 change: 0 additions & 1 deletion packages/layout/src/components/SiderMenu/BaseMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { MenuProps } from 'antd';
import { Menu, Skeleton } from 'antd';
import type { ItemType } from 'antd/es/menu/hooks/useItems';
import classNames from 'classnames';
import warningOnce from 'rc-util/lib/warning';
import React, { useContext, useEffect, useMemo, useRef } from 'react';
import type { LayoutDesignToken } from '../../context/ProLayoutContext';
import { ProLayoutContext } from '../../context/ProLayoutContext';
Expand Down
41 changes: 38 additions & 3 deletions packages/provider/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,15 @@ export type BaseProFieldFC = {
text: React.ReactNode;
/** 放置到组件上 props */
fieldProps?: any;
/** 模式类型 */
/**
* 组件的渲染模式类型
* @option read 渲染只读模式
* @option edit 渲染编辑模式
* */
mode: ProFieldFCMode;
/** 简约模式 */
/**
* 简约模式
*/
plain?: boolean;
/** 轻量模式 */
light?: boolean;
Expand All @@ -87,13 +93,26 @@ export type ProFieldFCRenderProps = {
} & BaseProFieldFC;

export type ProRenderFieldPropsType = {
/**
* 自定义只读模式的渲染器
* @params props 关于dom的配置
* @params dom 默认的 dom
* @return 返回一个用于读的 dom
*/
render?:
| ((
text: any,
props: Omit<ProFieldFCRenderProps, 'value' | 'onChange'>,
dom: JSX.Element,
) => JSX.Element)
| undefined;
/**
* 一个自定义的编辑渲染器。
* @params text 默认的值类型
* @params props 关于dom的配置
* @params dom 默认的 dom
* @return 返回一个用于编辑的dom
*/
renderFormItem?:
| ((text: any, props: ProFieldFCRenderProps, dom: JSX.Element) => JSX.Element)
| undefined;
Expand All @@ -104,6 +123,13 @@ export type IntlType = {
getMessage: (id: string, defaultMessage: string) => string;
};

/**
* 安全的从一个对象中读取相应的值
* @param source
* @param path
* @param defaultValue
* @returns
*/
function get(
source: Record<string, unknown>,
path: string,
Expand All @@ -125,7 +151,7 @@ function get(
}

/**
* 创建一个操作函数
* 创建一个国际化的操作函数
*
* @param locale
* @param localeMap
Expand Down Expand Up @@ -218,12 +244,16 @@ export {
intlMapKeys,
};

/**
* 国际化的配置类型
*/
export type ConfigContextPropsType = {
intl: IntlType;
isDeps: boolean;
valueTypeMap: Record<string, ProRenderFieldPropsType>;
};

/* Creating a context object with the default values. */
const ConfigContext = React.createContext<ConfigContextPropsType>({
intl: {
...zhCNIntl,
Expand Down Expand Up @@ -339,6 +369,11 @@ export const ConfigProviderWrap: React.FC<Record<string, unknown>> = ({
return <SWRConfig value={{ provider: () => new Map() }}>{configProviderDom}</SWRConfig>;
};

/**
* It returns the intl object from the context if it exists, otherwise it returns the intl object for
* the current locale
* @returns The return value of the function is the intl object.
*/
export function useIntl(): IntlType {
const { locale } = useContext(AntdConfigProvider.ConfigContext);
const { intl } = useContext(ConfigContext);
Expand Down
Loading

0 comments on commit b0b153b

Please sign in to comment.