fix(web-admin): allow multi-brand chips when plan brand is required
Treat required like any in BrandQtyChipsField so preference brand is not locked. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
156e4b63e5
commit
b28cf4a335
1 file changed
+64
-37
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user