Files
shanxia2.0/web-admin/src/composables/useDecimalInput.ts
T
2026-08-27 19:38:12 +08:00

82 lines
2.4 KiB
TypeScript

import { ref, watch, type Ref } from 'vue'
/** 是否为可提交的完整非负小数(空字符串视为清空) */
export function isCompleteDecimalInput(raw: string): boolean {
const trimmed = raw.trim()
return trimmed === '' || /^\d+(\.\d+)?$/.test(trimmed)
}
export function parseDecimalInput(raw: string, precision?: number): number | null {
const trimmed = raw.trim()
if (!trimmed) return null
if (!/^\d+(\.\d+)?$/.test(trimmed)) return null
const num = Number(trimmed)
if (!Number.isFinite(num) || num < 0) return null
if (precision == null) return num
return Number(num.toFixed(precision))
}
function formatDecimalDisplay(val: number, precision?: number): string {
if (precision == null) return String(val)
return Number(val.toFixed(precision)).toString()
}
function sanitizeDecimalText(raw: string): string {
let text = raw.replace(/[^\d.]/g, '')
const dotIndex = text.indexOf('.')
if (dotIndex >= 0) {
text = text.slice(0, dotIndex + 1) + text.slice(dotIndex + 1).replace(/\./g, '')
}
return text
}
/**
* 将 number | null 与 el-input 文本双向绑定;
* 仅在完整小数或清空时写入 source,避免打断输入,同时保证运费计算 watch 能触发。
*/
export function useDecimalInputBinding(
source: Ref<number | null>,
options?: { precision?: number; onCommit?: () => void }
) {
const text = ref('')
watch(
source,
(val) => {
const next =
val == null || Number.isNaN(Number(val))
? ''
: formatDecimalDisplay(Number(val), options?.precision)
if (text.value !== next) text.value = next
},
{ immediate: true }
)
function commitFromText(raw: string, force = false) {
const sanitized = sanitizeDecimalText(raw)
text.value = sanitized
if (!force && !isCompleteDecimalInput(sanitized)) return
const parsed = parseDecimalInput(sanitized, options?.precision)
const prev = source.value
const changed =
parsed == null ? prev != null : prev == null || Math.abs(Number(prev) - parsed) > 1e-9
source.value = parsed
if (parsed != null) {
text.value = formatDecimalDisplay(parsed, options?.precision)
} else if (force) {
text.value = ''
}
if (changed) options?.onCommit?.()
}
function handleInput(val: string) {
commitFromText(val, false)
}
function handleBlur() {
commitFromText(text.value, true)
}
return { text, handleInput, handleBlur }
}