|
|
@@ -1,9 +1,7 @@
|
|
|
<template>
|
|
|
<div v-if="brandMode" class="brand-qty-chips" :class="`brand-qty-chips--${variant}`">
|
|
|
<div class="brand-qty-chips__head">
|
|
|
- <h4 class="brand-qty-chips__title">{{
|
|
|
- brandMode === 'required' ? '指定品牌填量' : '选择品牌并填量'
|
|
|
- }}</h4>
|
|
|
+ <h4 class="brand-qty-chips__title">选择品牌并填量</h4>
|
|
|
<span
|
|
|
class="brand-qty-chips__sum"
|
|
|
:class="{ 'brand-qty-chips__sum--ok': sumMatch, 'brand-qty-chips__sum--bad': !sumMatch }"
|
|
|
@@ -12,7 +10,10 @@
|
|
|
</span>
|
|
|
</div>
|
|
|
|
|
|
- <div v-if="brandMode === 'any'" class="brand-qty-chips__chips">
|
|
|
+ <div
|
|
|
+ v-if="brandMode === 'any' || brandMode === 'required'"
|
|
|
+ class="brand-qty-chips__chips"
|
|
|
+ >
|
|
|
<button
|
|
|
v-for="opt in brandOptions"
|
|
|
:key="opt.brandId"
|
|
|
@@ -29,12 +30,6 @@
|
|
|
</button>
|
|
|
</div>
|
|
|
|
|
|
- <div v-else-if="brandMode === 'required'" class="brand-qty-chips__chips">
|
|
|
- <span class="brand-qty-chips__chip brand-qty-chips__chip--on brand-qty-chips__chip--locked">
|
|
|
- {{ requiredBrandLabel }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
-
|
|
|
<div class="brand-qty-chips__rows">
|
|
|
<div v-for="row in activeRows" :key="row.brandId" class="brand-qty-chips__row">
|
|
|
<span class="brand-qty-chips__row-label">{{ row.brandName }}</span>
|
|
|
@@ -52,15 +47,14 @@
|
|
|
</template>
|
|
|
<template v-else>
|
|
|
<input
|
|
|
- type="number"
|
|
|
+ type="text"
|
|
|
inputmode="decimal"
|
|
|
- step="0.001"
|
|
|
- min="0"
|
|
|
class="brand-qty-chips__qty-input"
|
|
|
- :value="row.qty === 0 ? '' : row.qty"
|
|
|
+ :value="qtyDisplay(row.brandId)"
|
|
|
:disabled="disabled"
|
|
|
placeholder="0"
|
|
|
@input="onMobileQtyInput(row.brandId, $event)"
|
|
|
+ @blur="onMobileQtyBlur(row.brandId)"
|
|
|
/>
|
|
|
</template>
|
|
|
<span v-if="unit" class="brand-qty-chips__unit">{{ unit }}</span>
|
|
|
@@ -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<Record<string, string>>({})
|
|
|
+
|
|
|
+ /** 最多 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 {
|