diff --git a/web-admin/src/components/business/brand-qty-chips-field/index.vue b/web-admin/src/components/business/brand-qty-chips-field/index.vue index e2b34714d..86a12c65e 100644 --- a/web-admin/src/components/business/brand-qty-chips-field/index.vue +++ b/web-admin/src/components/business/brand-qty-chips-field/index.vue @@ -1,9 +1,7 @@ {{ unit }} @@ -107,23 +101,7 @@ 'update:actualQty': [value: number] }>() - const requiredBrandLabel = computed(() => { - const fromOptions = props.brandOptions.find((o) => o.brandId === props.requiredBrandId) - if (fromOptions?.brandName) return fromOptions.brandName - const fromRows = props.modelValue.find((r) => r.brandId === props.requiredBrandId) - return fromRows?.brandName || '指定品牌' - }) - - const activeRows = computed(() => { - if (props.brandMode === 'required') { - const id = props.requiredBrandId || props.brandOptions[0]?.brandId || '' - const existing = props.modelValue.find((r) => r.brandId === id) - if (existing) return [existing] - const opt = props.brandOptions.find((o) => o.brandId === id) - return [{ brandId: id, brandName: opt?.brandName || requiredBrandLabel.value, qty: 0 }] - } - return props.modelValue - }) + const activeRows = computed(() => props.modelValue) function isSelected(brandId: string) { return props.modelValue.some((r) => r.brandId === brandId) @@ -160,15 +138,37 @@ } function toggleChip(opt: BrandOption) { - if (props.disabled || props.brandMode !== 'any') return + if (props.disabled || (props.brandMode !== 'any' && props.brandMode !== 'required')) return const selected = isSelected(opt.brandId) if (selected) { + clearQtyDraft(opt.brandId) emitRows(props.modelValue.filter((r) => r.brandId !== opt.brandId)) return } emitRows([...props.modelValue, { brandId: opt.brandId, brandName: opt.brandName, qty: 0 }]) } + /** 输入中草稿:保留「20.」「20.0」等中间态,避免 Number() 立刻收成 20 */ + const qtyDrafts = ref>({}) + + /** 最多 3 位小数的非负数量中间态(允许空、单独小数点、尾随小数点) */ + const QTY_DRAFT_RE = /^\d*\.?\d{0,3}$/ + + function qtyDisplay(brandId: string) { + if (Object.prototype.hasOwnProperty.call(qtyDrafts.value, brandId)) { + return qtyDrafts.value[brandId] + } + const qty = qtyOf(brandId) + return qty === 0 ? '' : String(qty) + } + + function parseQtyDraft(raw: string): number { + if (raw === '' || raw === '.') return 0 + const n = Number(raw) + if (Number.isNaN(n) || n < 0) return 0 + return Number(n.toFixed(3)) + } + function setQty(brandId: string, qty: number) { const next = props.modelValue.map((r) => r.brandId === brandId ? { ...r, qty: Math.max(0, qty) } : r @@ -176,10 +176,29 @@ emitRows(next) } + function clearQtyDraft(brandId: string) { + if (!Object.prototype.hasOwnProperty.call(qtyDrafts.value, brandId)) return + const next = { ...qtyDrafts.value } + delete next[brandId] + qtyDrafts.value = next + } + function onMobileQtyInput(brandId: string, e: Event) { - const raw = (e.target as HTMLInputElement).value - const qty = raw === '' ? 0 : Math.max(0, Number(raw) || 0) - setQty(brandId, qty) + const raw = (e.target as HTMLInputElement).value.trim() + if (raw !== '' && !QTY_DRAFT_RE.test(raw)) { + // 非法字符:保持草稿/已有展示,阻止浏览器写入非法值 + ;(e.target as HTMLInputElement).value = qtyDisplay(brandId) + return + } + qtyDrafts.value = { ...qtyDrafts.value, [brandId]: raw } + setQty(brandId, parseQtyDraft(raw)) + } + + function onMobileQtyBlur(brandId: string) { + if (!Object.prototype.hasOwnProperty.call(qtyDrafts.value, brandId)) return + const normalized = parseQtyDraft(qtyDrafts.value[brandId]) + clearQtyDraft(brandId) + setQty(brandId, normalized) } watch( @@ -192,9 +211,10 @@ const existing = props.modelValue.find((r) => r.brandId === id) if (!existing) { emitRows([ + ...props.modelValue, { brandId: id, - brandName: opt?.brandName || requiredBrandLabel.value, + brandName: opt?.brandName || '指定品牌', qty: 0 } ]) @@ -294,6 +314,7 @@ grid-template-columns: 72px 1fr auto; gap: 6px; align-items: center; + min-width: 0; } .brand-qty-chips__row-label { @@ -334,8 +355,14 @@ } .brand-qty-chips--admin { + .brand-qty-chips__rows { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px 16px; + } + .brand-qty-chips__row { - grid-template-columns: 100px 1fr auto; + grid-template-columns: minmax(64px, 100px) minmax(0, 1fr) auto; } .brand-qty-chips__admin-input {