82 lines
2.4 KiB
TypeScript
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 }
|
|
}
|