---
alwaysApply: true
---
## useInputNumber 使用规则

### 🎯 核心原则
- **输入清洗**：自动过滤非数字字符（根据配置保留小数点和负号）。
- **响应式同步**：支持传入 `Ref` 或 `Getter` 作为输入源，实现极简的组件状态对接。
- **格式化显示**：支持千分位分隔符和固定精度显示。
- **边界控制**：通过 `max` (默认 `Infinity`) 和 `min` (默认 `-Infinity`) 自动修正数值范围。
- **第三方兼容**：提供 `formatter` 和 `parser` 以完美适配 Element Plus 等 UI 框架。

### 📝 使用规范

#### 1. 基础用法
```typescript
import { useInputNumber } from 'xn-fe-tools'

// 推荐用法：第一个参数传入数据源（支持 Ref 或 Getter），第二个参数传入配置
const { value, displayValue, handleInput, handleBlur } = useInputNumber(() => props.modelValue, {
  precision: 2,
  showThousands: true
})
```

#### 2. 参数说明 (UseInputNumberOptions)
| 参数名 | 类型 | 默认值 | 说明 |
|-------|------|--------|------|
| `precision` | `number` | `0` | 小数点保留位数 |
| `decimalLimitMode` | `'discard' \| 'ceil' \| 'round'` | `'discard'` | 小数位超限时的处理方式（丢弃/向上取整/四舍五入） |
| `autoFixed` | `boolean` | `true` | 失焦时是否根据精度自动补全小数点后的 0 |
| `max` | `number` | `Infinity` | 允许的最大值 |
| `min` | `number` | `-Infinity` | 允许的最小值 |
| `allowNegative` | `boolean` | `false` | 是否允许输入负数 |
| `showThousands` | `boolean` | `false` | 是否显示千分位分隔符 |

#### 3. 返回值说明
| 属性名 | 类型 | 说明 |
|-------|------|------|
| `value` | `Ref<string>` | 内部维护的纯净数值字符串（无千分位） |
| `displayValue` | `ComputedRef<string>` | 格式化后的显示字符串 |
| `handleInput` | `(val) => void` | 输入处理函数，直接绑定到输入框事件 |
| `handleBlur` | `() => void` | 失去焦点处理函数，执行边界检查和补全 |
| `formatter` | `(val) => string` | 格式化器，适配 `el-input` 的 `formatter` 属性 |
| `parser` | `(val) => string` | 解析器，适配 `el-input` 的 `parser` 属性 |
| `clean` | `(val) => string` | 核心清洗函数，用于提取字符串中的合法数字部分 |

### 🚨 最佳实践

- **组件封装模式**：
  - **Props 传递**：直接将 `props` 对象作为第二个参数传入 Hook，并使用 `() => props.modelValue` 作为第一个参数，确保响应式链路完整。
  - **双向绑定**：务必使用 `watch` 监听内部 `value` 的变化，并触发 `emits('update:modelValue', val)` 来同步父组件数据。同理可处理 `displayValue`。
- **UI 库适配**：
  - **Element Plus**：推荐使用 `v-model="value"` 绑定内部值，并透传 Hooks 返回的 `formatter` 和 `parser` 属性。
  - **Ant Design Vue**：由于 `a-input` 的格式化机制差异，**必须**拦截 `@change` 事件。在事件处理中，使用 `clean` 函数计算光标前的有效字符数，并在更新值后手动恢复光标位置，以防止光标跳动。
- **类型安全**：Hook 内部处理的是字符串逻辑，如需进行数学计算，请在业务层通过 `Number(value.value)` 或 `parseFloat` 转换。

### 📋 模版代码

#### 1. Element Plus 模式 (核心：`formatter`/`parser`)
```vue
<script setup>
import { watch } from 'vue'
import { useInputNumber } from 'xn-fe-tools'

const props = defineProps(['modelValue', 'precision', 'showThousands']);
const emits = defineEmits(['update:modelValue', 'change']);

const { value, handleBlur, formatter, parser } = useInputNumber(() => props.modelValue, props);

watch(value, (val) => {
  emits('update:modelValue', val);
  emits('change', val);
});
</script>

<template>
  <el-input v-model="value" @blur="handleBlur" :formatter="formatter" :parser="parser" />
</template>
```

#### 2. Ant Design Vue 模式 (核心：手动光标同步)
```vue
<script setup>
import { watch } from 'vue'
import { useInputNumber } from 'xn-fe-tools'

const props = defineProps(['modelValue']);
const emits = defineEmits(['update:modelValue']);

const { value, displayValue, handleInput, clean, handleBlur } = useInputNumber(() => props.modelValue, props);

const handleChange = (e) => {
  const inputEl = e.target;
  const oldRawValue = inputEl.value;
  const oldCursorPos = inputEl.selectionStart || 0;
  
  // 计算清洗前光标前的有效数字长度
  const validBeforeCursor = clean(oldRawValue.slice(0, oldCursorPos)).length;
  handleInput(oldRawValue);

  // 强制同步 DOM 并恢复光标位置
  if (inputEl.value !== displayValue.value) {
    inputEl.value = displayValue.value;
    let newPos = 0, validCount = 0;
    while (validCount < validBeforeCursor && newPos < displayValue.value.length) {
      if (/[\d.-]/.test(displayValue.value[newPos])) validCount++;
      newPos++;
    }
    inputEl.setSelectionRange(newPos, newPos);
  }
};

watch(value, (val) => emits('update:modelValue', val));
</script>

<template>
  <a-input :value="displayValue" @change="handleChange" @blur="handleBlur" />
</template>
```