---
nav:
  title: 组件
  order: 1
group:
  title: General
  order: 1
title: PisellNumber
order: 1
category: pro
---

# PisellNumber 数值组件

## 组件介绍

PisellNumber 是 Pisell 2.0 组件库中处理数值数据的基础组件。它提供了数值展示、输入、校验、排序和筛选等核心能力，适用于数量、次数、库存、排序权重等通用数值场景。

**核心特性**：

- ✅ 三种交互状态：只读、编辑、禁用
- ✅ 精度控制：支持小数位数配置
- ✅ 千分位分隔符：符合财务规范
- ✅ 完整的校验框架：必填、范围、自定义校验
- ✅ 受控和非受控模式
- ✅ 基于 Ant Design InputNumber 开发

---

## 基础用法

### 基础示例（受控模式）

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(10);

    return (
        <PisellNumber
            value={value}
            onChange={setValue}
            placeholder="请输入数字"
        />
    );
};
```

### 非受控模式

```tsx
import {PisellNumber} from '@pisell/materials';

export default () => {
    return (
        <PisellNumber
            defaultValue={10}
            placeholder="请输入数字"
        />
    );
};
```

### 只读态展示

```tsx
import {PisellNumber} from '@pisell/materials';

export default () => {
    return (
        <PisellNumber
            mode="read"
            value={1234.56}
            precision={2}
        />
    );
};
```

### 禁用态

```tsx
import {PisellNumber} from '@pisell/materials';

export default () => {
    return (
        <PisellNumber
            disabled
            value={100}
        />
    );
};
```

---

## 数值范围与精度

### 范围限制

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(50);

    return (
        <PisellNumber
            value={value}
            onChange={setValue}
            min={0}
            max={100}
            step={10}
        />
    );
};
```

### 精度控制

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [int, setInt] = useState<number | null>(100);
    const [decimal2, setDecimal2] = useState<number | null>(12.34);
    const [decimal3, setDecimal3] = useState<number | null>(12.345);

    return (
        <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
            {/* 整数（精度为 0） */}
            <PisellNumber
                value={int}
                onChange={setInt}
                precision={0}
            />

            {/* 保留 2 位小数 */}
            <PisellNumber
                value={decimal2}
                onChange={setDecimal2}
                precision={2}
                step={0.01}
            />

            {/* 保留 3 位小数 */}
            <PisellNumber
                value={decimal3}
                onChange={setDecimal3}
                precision={3}
                step={0.001}
            />
        </div>
    );
};
```

### 负数支持

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [negative, setNegative] = useState<number | null>(0);
    const [positive, setPositive] = useState<number | null>(10);

    return (
        <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
            {/* 允许负数 */}
            <PisellNumber
                value={negative}
                onChange={setNegative}
                min={-100}
                max={100}
                placeholder="允许负数"
            />

            {/* 仅正数 */}
            <PisellNumber
                value={positive}
                onChange={setPositive}
                min={0}
                placeholder="仅正数"
            />
        </div>
    );
};
```

---

## 格式化展示

### 千分位分隔符

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(1234567.89);

    return (
        <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
            {/* 只读态显示千分位 */}
            <PisellNumber
                mode="read"
                value={value}
                precision={2}
                useGrouping
            />

            {/* 编辑态也支持千分位 */}
            <PisellNumber
                value={value}
                onChange={setValue}
                precision={2}
                useGrouping
            />
        </div>
    );
};
```

### 自定义格式化

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(100);

    return (
        <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
            {/* 自定义格式化函数 */}
            <PisellNumber
                mode="read"
                value={value}
                formatter={(value) => {
                    return value ? `$ ${value}` : '';
                }}
            />

            {/*自定义解析函数（编辑态需要）*/}
            <PisellNumber
                value={value}
                onChange={setValue}
                formatter={(value) => {
                    return value ? `� ${value}` : '';
                }}
                parser={(displayValue) => {
                    return displayValue ? parseFloat(displayValue.replace('¥', '').trim()) : null;
                }}
            />
        </div>
    );
};
```

---

## 前后缀与标签

### 前缀和后缀（输入框内部）

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [v1, setV1] = useState<number | null>(100);
    const [v2, setV2] = useState<number | null>(100);
    const [v3, setV3] = useState<number | null>(100);

    return (
        <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
            {/* 前缀 */}
            <PisellNumber
                value={v1}
                onChange={setV1}
                prefix="￥"
            />

            {/* 后缀 */}
            <PisellNumber
                value={v2}
                onChange={setV2}
                suffix="元"
            />

            {/* 前后缀组合 */}
            <PisellNumber
                value={v3}
                onChange={setV3}
                prefix="�"
                suffix=".00"
            />
        </div>
    );
};
```

### 前后置标签（输入框外部）

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [v1, setV1] = useState<number | null>(1000);
    const [v2, setV2] = useState<number | null>(50);
    const [v3, setV3] = useState<number | null>(25);

    return (
        <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
            {/* 前置标签 */}
            <PisellNumber
                value={v1}
                onChange={setV1}
                addonBefore="金额"
            />

            {/* 后置标签 */}
            <PisellNumber
                value={v2}
                onChange={setV2}
                addonAfter="kg"
            />

            {/* 前后置标签组合 */}
            <PisellNumber
                value={v3}
                onChange={setV3}
                addonBefore="单价"
                addonAfter="元/kg"
            />
        </div>
    );
};
```

---

## 校验功能

### 必填校验

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(null);

    return (
        <PisellNumber
            value={value}
            onChange={setValue}
            required
            validateTrigger="both"
            onValidate={(isValid, errors) => {
                console.log('校验结果', isValid, errors);
            }}
            placeholder="必填校验"
        />
    );
};
```

### 范围校验

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(null);

    return (
        <PisellNumber
            value={value}
            onChange={setValue}
            min={0}
            max={100}
            required
            validateTrigger="onChange"
        />
    );
};
```

### 自定义校验

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [evenNum, setEvenNum] = useState<number | null>(null);
    const [positiveNum, setPositiveNum] = useState<number | null>(null);

    return (
        <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
            {/* 偶数校验 */}
            <PisellNumber
                value={evenNum}
                onChange={setEvenNum}
                validator={(v) => {
                    if (v === null) return true;
                    return v % 2 === 0 || '请输入偶数';
                }}
                validateTrigger="both"
                placeholder="?????"
            />

            {/* 正数校验 */}
            <PisellNumber
                value={positiveNum}
                onChange={setPositiveNum}
                validator={(v) => {
                    if (v === null) return true;
                    return v > 0 || '必须输入正数';
                }}
                placeholder="?????"
            />
        </div>
    );
};
```

### 校验触发时机

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [v1, setV1] = useState<number | null>(null);
    const [v2, setV2] = useState<number | null>(null);
    const [v3, setV3] = useState<number | null>(null);

    return (
        <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
            {/* 输入时校验 */}
            <PisellNumber
                value={v1}
                onChange={setV1}
                required
                validateTrigger="onChange"
            />

            {/* 失焦时校验 */}
            <PisellNumber
                value={v2}
                onChange={setV2}
                required
                validateTrigger="onBlur"
            />

            {/* 输入和失焦时都校验 */}
            <PisellNumber
                value={v3}
                onChange={setV3}
                required
                validateTrigger="both"
            />
        </div>
    );
};
```

### 自定义错误提示

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(null);

    return (
        <PisellNumber
            value={value}
            onChange={setValue}
            required
            errorMessage="请输入有效的数量"
            placeholder="请输入有效的数量"
        />
    );
};
```

---

## 表单场景

### 在 Form 中使用

```tsx
import {Form} from 'antd';
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [quantity, setQuantity] = useState<number | null>(1);
    const [price, setPrice] = useState<number | null>(0);
    const [weight, setWeight] = useState<number | null>(0);

    return (
        <Form layout="vertical">
            <Form.Item label="购买数量" required>
                <PisellNumber
                    value={quantity}
                    onChange={setQuantity}
                    min={1}
                    max={100}
                    step={1}
                    precision={0}
                    required
                    placeholder="请输入数量"
                />
            </Form.Item>

            <Form.Item label="商品价格" required>
                <PisellNumber
                    value={price}
                    onChange={setPrice}
                    min={0}
                    precision={2}
                    addonBefore="￥"
                    required
                    placeholder="?????"
                />
            </Form.Item>

            <Form.Item label="商品重量">
                <PisellNumber
                    value={weight}
                    onChange={setWeight}
                    min={0}
                    precision={3}
                    addonAfter="kg"
                    placeholder="请输入重量"
                />
            </Form.Item>
        </Form>
    );
};
```

---

## 表格场景

### 表格列中使用（只读态）

```tsx
import {Table} from 'antd';
import {PisellNumber} from '@pisell/materials';

export default () => {
    const columns = [
        {
            title: '商品名称',
            dataIndex: 'name',
        },
        {
            title: '库存数量',
            dataIndex: 'stock',
            render: (value: number) => (
                <PisellNumber
                    mode="read"
                    value={value}
                    precision={0}
                />
            ),
            sorter: (a, b) => a.stock - b.stock,
        },
        {
            title: '单价',
            dataIndex: 'price',
            render: (value: number) => (
                <PisellNumber
                    mode="read"
                    value={value}
                    precision={2}
                    useGrouping
                    addonBefore="￥"
                />
            ),
            sorter: (a, b) => a.price - b.price,
        },
    ];

    const dataSource = [
        {id: 1, name: '商品A', stock: 100, price: 1234.56},
        {id: 2, name: '商品B', stock: 50, price: 678.90},
    ];

    return <Table dataSource={dataSource} columns={columns}/>;
};
```

---

## 只读态样式配置

### 字体样式

```tsx
import {PisellNumber} from '@pisell/materials';

export default () => {
    return (
        <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
            {/*  字号 */}
            <PisellNumber
                mode="read"
                value={1234.56}
                fontSize={20}
            />

            {/* 字重 */}
            <PisellNumber
                mode="read"
                value={1234.56}
                fontWeight="bold"
            />

            {/* 颜色 */}
            <PisellNumber
                mode="read"
                value={1234.56}
                color="#ff4d4f"
            />

            {/* 组合使用 */}
            <PisellNumber
                mode="read"
                value={1234.56}
                fontSize={18}
                fontWeight={600}
                color="#52c41a"
            />
        </div>
    );
};
```

### 对齐方式

```tsx
import {PisellNumber} from '@pisell/materials';

export default () => {
    return (
        <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
            {/* 左对齐（默认） */}
            <PisellNumber
                mode="read"
                value={1234.56}
                textAlign="left"
            />

            {/* 居中对齐 */}
            <PisellNumber
                mode="read"
                value={1234.56}
                textAlign="center"
                style={{width: 200}}
            />

            {/* 右对齐 */}
            <PisellNumber
                mode="read"
                value={1234.56}
                textAlign="right"
                style={{width: 200}}
            />
        </div>
    );
};
```

---

## 编辑态配置

### 不同尺寸

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(100);

    return (
        <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
            {/*大尺寸*/}
            <PisellNumber value={value} onChange={setValue} size="large"/>

            {/* 中等尺寸（默认） */}
            <PisellNumber value={value} onChange={setValue} size="middle"/>

            {/* 小尺寸 */}
            <PisellNumber value={value} onChange={setValue} size="small"/>
        </div>
    );
};
```

### 其他配置

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [v1, setV1] = useState<number | null>(100);
    const [v2, setV2] = useState<number | null>(200);
    const [v3, setV3] = useState<number | null>(300);
    const [v4, setV4] = useState<number | null>(400);

    return (
        <div style={{display: 'flex', flexDirection: 'column', gap: 16}}>
            {/* 显示清空按钮 */}
            <PisellNumber
                value={v1}
                onChange={setV1}
                allowClear
            />

            {/* 自动聚焦 */}
            <PisellNumber
                value={v2}
                onChange={setV2}
                autoFocus
            />

            {/* 禁用键盘上下键 */}
            <PisellNumber
                value={v3}
                onChange={setV3}
                keyboard={false}
            />

            {/* 隐藏步进器 */}
            <PisellNumber
                value={v4}
                onChange={setV4}
                controls={false}
            />
        </div>
    );
};
```

---

## 事件回调

### 值变化回调

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(null);

    return (
        <PisellNumber
            value={value}
            onChange={(newValue) => {
                console.log('新值：', newValue);
                setValue(newValue);
            }}
        />
    );
};
```

### 失焦回调

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(null);

    return (
        <PisellNumber
            value={value}
            onChange={setValue}
            onBlur={(currentValue) => {
                console.log('失焦时的值：', currentValue);
            }}
        />
    );
};
```

### 聚焦回调

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(null);

    return (
        <PisellNumber
            value={value}
            onChange={setValue}
            onFocus={() => {
                console.log('输入框已聚焦');
            }}
        />
    );
};
```

### 回车键回调

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(null);

    return (
        <PisellNumber
            value={value}
            onChange={setValue}
            onPressEnter={(currentValue) => {
                console.log('按下回车，当前值:', currentValue);
            }}
        />
    );
};
```

### 校验回调

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(null);

    return (
        <PisellNumber
            value={value}
            onChange={setValue}
            required
            min={0}
            max={100}
            validateTrigger="both"
            onValidate={(isValid, errors) => {
                if (!isValid) {
                    console.error('校验失败：', errors);
                } else {
                    console.log('校验通过');
                }
            }}
        />
    );
};
```

---

## 空值处理

### 默认空值显示

```tsx
import {PisellNumber} from '@pisell/materials';

export default () => {
    return (
        <PisellNumber
            mode="read"
            value={null}
        />
    );
};
```

### 自定义空值文本

```tsx
import {PisellNumber} from '@pisell/materials';

export default () => {
    return (
        <PisellNumber
            mode="read"
            value={null}
            emptyText="暂无数据"
        />
    );
};
```

---

## 样式定制

### 自定义类名和样式

```tsx
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [value, setValue] = useState<number | null>(100);

    return (
        <PisellNumber
            value={value}
            onChange={setValue}
            className="custom-number-input"
            style={{width: 300}}
        />
    );
};
```

### 只读态样式定制

```tsx
import {PisellNumber} from '@pisell/materials';

export default () => {
    return (
        <PisellNumber
            mode="read"
            value={1234.56}
            className="custom-text"
            style={{
                fontSize: 18,
                fontWeight: 'bold',
                color: '#1890ff',
                textAlign: 'right',
            }}
        />
    );
};
```

---

## 完整示例

### 商品数量输入

```tsx
import {Form} from 'antd';
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [quantity, setQuantity] = useState<number | null>(1);

    return (
        <Form.Item
            label="购买数量"
            required
            validateStatus={quantity && quantity > 0 ? 'success' : 'error'}
        >
            <PisellNumber
                value={quantity}
                onChange={setQuantity}
                min={1}
                max={999}
                step={1}
                precision={0}
                required
                placeholder="请输入数量"
                validateTrigger="both"
                onValidate={(isValid, errors) => {
                    if (!isValid) {
                        console.error('数量校验失败:', errors);
                    }
                }}
            />
        </Form.Item>
    );
};
```

### 价格输入

```tsx
import {Form} from 'antd';
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [price, setPrice] = useState<number | null>(null);

    return (
        <Form.Item label="商品价格" required>
            <PisellNumber
                value={price}
                onChange={setPrice}
                min={0}
                max={999999.99}
                precision={2}
                step={0.01}
                addonBefore="￥"
                required
                placeholder="请输入价格"
                validateTrigger="both"
            />
        </Form.Item>
    );
};
```

### 重量输入

```tsx
import {Form} from 'antd';
import {PisellNumber} from '@pisell/materials';
import {useState} from 'react';

export default () => {
    const [weight, setWeight] = useState<number | null>(null);

    return (
        <Form.Item label="商品重量">
            <PisellNumber
                value={weight}
                onChange={setWeight}
                min={0}
                precision={3}
                step={0.001}
                addonAfter="kg"
                placeholder="请输入重量"
            />
        </Form.Item>
    );
};
```

### 统计数据展示

```tsx
import {PisellNumber} from '@pisell/materials';

export default () => {
    return (
        <div style={{display: 'flex', gap: 32}}>
            <div>
                <div style={{color: '#667085', marginBottom: 8}}>总订单数</div>
                <PisellNumber
                    mode="read"
                    value={12345}
                    precision={0}
                    useGrouping
                    fontSize={24}
                    fontWeight="bold"
                    color="#101828"
                />
            </div>

            <div>
                <div style={{color: '#667085', marginBottom: 8}}>总销售额</div>
                <PisellNumber
                    mode="read"
                    value={1234567.89}
                    precision={2}
                    useGrouping
                    fontSize={24}
                    fontWeight="bold"
                    color="#52c41a"
                />
            </div>
        </div>
    );
};
```

---

## API 文档

### Props 属性

| 属性名              | 类型                                             | 默认值        | 说明           |
|------------------|------------------------------------------------|------------|--------------|
| **基础属性**         |                                                |            |              |
| mode             | `'read' \| 'edit'`                             | `'edit'`   | 显示模式         |
| disabled         | `boolean`                                      | `false`    | 是否禁用         |
| value            | `number \| null`                               | -          | 当前值（受控）      |
| defaultValue     | `number \| null`                               | -          | 默认值（非受控）     |
| placeholder      | `string`                                       | `'请输入数字'`  | 占位文本         |
| **数值配置**         |                                                |            |              |
| min              | `number`                                       | -          | 最小值          |
| max              | `number`                                       | -          | 最大值          |
| step             | `number`                                       | `1`        | 步进值          |
| precision        | `number`                                       | -          | 小数精度         |
| useGrouping      | `boolean`                                      | `false`    | 是否使用千分位分隔符   |
| **格式化**          |                                                |            |              |
| formatter        | `(value: number \| null) => string`            | -          | 自定义格式化函数     |
| parser           | `(displayValue: string) => number \| null`     | -          | 自定义解析函数      |
| **校验配置**         |                                                |            |              |
| required         | `boolean`                                      | `false`    | 是否必填         |
| validator        | `(value: number \| null) => boolean \| string` | -          | 自定义校验函数      |
| validateTrigger  | `'onChange' \| 'onBlur' \| 'both'`             | `'onBlur'` | 校验触发时机       |
| errorMessage     | `string`                                       | -          | 自定义错误提示      |
| autoCorrect      | `boolean`                                      | `false`    | 是否自动修正超出范围的值 |
| **UI 配置**        |                                                |            |              |
| size             | `'large' \| 'middle' \| 'small'`               | `'middle'` | 输入框尺寸        |
| allowClear       | `boolean`                                      | `false`    | 是否显示清空按钮     |
| autoFocus        | `boolean`                                      | `false`    | 是否自动聚焦       |
| keyboard         | `boolean`                                      | `true`     | 是否启用键盘上下键    |
| controls         | `boolean \| object`                            | `true`     | 是否显示步进器      |
| prefix           | `ReactNode`                                    | -          | 前缀           |
| suffix           | `ReactNode`                                    | -          | 后缀           |
| addonBefore      | `ReactNode`                                    | -          | 前置标签         |
| addonAfter       | `ReactNode`                                    | -          | 后置标签         |
| **只读态配置**        |                                                |            |              |
| fontSize         | `string \| number`                             | -          | 字号（只读态）      |
| fontWeight       | `string \| number`                             | -          | 字重（只读态）      |
| color            | `string`                                       | -          | 文本颜色（只读态）    |
| textAlign        | `'left' \| 'center' \| 'right'`                | `'left'`   | 文本对齐（只读态）    |
| emptyText        | `string`                                       | `'-'`      | 空值显示文本（只读态）  |
| **样式**           |                                                |            |              |
| className        | `string`                                       | -          | 自定义类名        |
| style            | `CSSProperties`                                | -          | 自定义样式        |
| **无障碍**          |                                                |            |              |
| aria-label       | `string`                                       | -          | ARIA 标签      |
| aria-describedby | `string`                                       | -          | ARIA 描述      |
| id               | `string`                                       | -          | HTML id 属性   |
| name             | `string`                                       | -          | HTML name 属性 |

### ????

| ???          | ??                                             | ??       |
|--------------|------------------------------------------------|----------|
| onChange     | `(value: number \| null) => void`              | ???????  |
| onBlur       | `(value: number \| null) => void`              | ??????   |
| onFocus      | `() => void`                                   | ??????   |
| onPressEnter | `(value: number \| null) => void`              | ???????? |
| onValidate   | `(isValid: boolean, errors: string[]) => void` | ????     |

---

## 常见问题

### 1. 如何控制小数位数？

使用 `precision` 属性：

```
<PisellNumber
    value={value}
    onChange={setValue}
    precision={2}
/>
```

### 2. 如何只允许整数？

设置 `precision={0}`：

```
<PisellNumber
    value={value}
    onChange={setValue}
    precision={0}
/>
```

### 3. 如何实现千分位分隔符？

设置 `useGrouping={true}`：

```
<PisellNumber
    mode="read"
    value={1234567}
    useGrouping
/>
// 显示: 1,234,567
```

### 4. 如何限制输入范围？

使用 `min` 和 `max` 属性：

```
<PisellNumber
    value={value}
    onChange={setValue}
    min={0}
    max={100}
/>
```

### 5. 如何添加货币符号？

使用 `addonBefore` 或 `addonAfter`：

```
<PisellNumber
    value={value}
    onChange={setValue}
    addonBefore="￥"
/>
```

或者使用自定义格式化（只读态）：

```
<PisellNumber
    mode="read"
    value={100}
    formatter={(v) => v ? `￥${v}` : ''}
/>
```

### 6. 如何校验数值？

支持多种校验方式：

```
// 必填校验
<PisellNumber required/>

// 范围校验
<PisellNumber min={0} max={100}/>

// 自定义校验
<PisellNumber
    validator={(v) => v > 0 || '必须大于0'}
/>
```

### 7. 受控模式和非受控模式有什么区别？

**受控模式**：值由外部管理

```
const [value, setValue] = useState(10);
<PisellNumber value={value} onChange={setValue}/>
```

**非受控模式**：值由组件内部管理

```
<PisellNumber defaultValue={10}/>
```

### 8. 如何在表格中使用？

只读态适合在表格中展示：

```
{
  title: '数量',
  dataIndex: 'quantity',
  render: (value) => (
    <PisellNumber mode="read" value={value} precision={0} />
  ),
}
```

---

## 最佳实践

1. **精度设置**：根据业务场景设置合适的精度

- 数量：`precision={0}`（整数）
- 价格：`precision={2}`（2位小数）
- 重量：`precision={3}`（3位小数）

2. **范围限制**：始终设置合理的 min/max，防止无效数据

3. **校验时机**：

- 即时反馈：`validateTrigger="onChange"`
- 延迟反馈：`validateTrigger="onBlur"`
- 两者结合：`validateTrigger="both"`

4. **只读态优化**：在表格、列表等展示场景使用 `mode="read"`，性能更好

5. **受控模式优先**：在表单场景推荐使用受控模式，便于统一管理

6. **千分位使用**：金额、大数值展示时启用 `useGrouping`

---

## 派生组件

PisellNumber 作为基础组件，可以派生出以下专用组件：

- **PisellCurrency**：货币组件（添加货币符号）
- **PisellPercent**：百分比组件（添加 % 符号）

这些派生组件继承了 PisellNumber 的所有功能，并添加了特定的业务语义。

---

## 注意事项

1. **类型安全**：value 的类型是 `number | null`，注意 null 值的处理
2. **精度损失**：JavaScript 浮点数计算可能有精度问题，建议使用 precision 避免
3. **负数处理**：如果业务不需要负数，设置 `min={0}`
4. **步进值**：step 应该与 precision 匹配（如 precision=2，step=0.01）
5. **格式化优先级**：自定义 formatter 优先于 useGrouping 和 precision

---

## 相关组件

- **PisellCurrency** - 货币组件
- **PisellPercent** - 百分比组件
- **PisellSingleLineText** - 单行文本组件
- **PisellLongText** - 长文本组件
