feat(product-family): auto-derive logistics/craft tags from family members

This commit is contained in:
yeuimu
2026-08-28 14:57:25 +08:00
parent a13931c84c
commit eedfbb344e
6 changed files with 340 additions and 13 deletions
+45 -4
View File
@@ -406,7 +406,7 @@ function collectSiblings(ogNode: any): any[] {
}
function openConfigModal(og: any, dropTarget: any) {
configOG.value = og
configOG.value = { ...og, familyId: og.familyId ?? findOgNodeById(og.rawId)?.familyId ?? null }
configDropTarget.value = dropTarget
const ogNode = findOgNodeById(og.rawId)
const siblings = ogNode ? collectSiblings(ogNode) : []
@@ -729,7 +729,11 @@ async function openEdit(g: Good) {
countryId: g.countryId,
cascaderCategory: findCategoryPath(allCategories.value, g.categoryId),
categoryId: g.categoryId,
tagIds: (g.tags || []).map(t => t.id),
// 有族时:自动组(物流/工艺/位置)标签为派生数据,不进入可编辑选择
tagIds: (g.tags || []).filter((t: any) => {
const og = (g as any).originGood?.family
return !og || !t.tagGroupId || !autoTagGroupIds.value.has(t.tagGroupId)
}).map((t: any) => t.id),
positionId: g.positionId || '',
}
initEditFamily((g as any).originGood?.family ?? null)
@@ -1289,6 +1293,29 @@ const groupedTagOptions = computed(() => {
return groups
})
// ─── 族派生标签:物流/工艺/位置组由族自动同步,表单中只读 ───
const isAutoTagGroup = (name: string) => /物流|工艺|位置/.test(name)
const autoTagGroupIds = computed(() =>
new Set(allTagGroups.value.filter((g) => isAutoTagGroup(g.groupName)).map((g) => g.id)),
)
const configHasFamily = computed(() => Boolean((configOG.value as any)?.familyId))
const configTagOptions = computed(() =>
configHasFamily.value
? groupedTagOptions.value.filter((g) => g.id === 'ungrouped' || !autoTagGroupIds.value.has(g.id as any))
: groupedTagOptions.value,
)
const editTagOptions = computed(() =>
editFamilyId.value
? groupedTagOptions.value.filter((g) => g.id === 'ungrouped' || !autoTagGroupIds.value.has(g.id as any))
: groupedTagOptions.value,
)
/** 当前商品的自动组标签(只读展示) */
const editDerivedTags = computed(() =>
editFamilyId.value
? ((editGood.value as any)?.tags ?? []).filter((t: any) => autoTagGroupIds.value.has(t.tagGroupId))
: [],
)
function toggleTagInSelection(tagId: string): void {
const idx = selectedTagIds.value.indexOf(tagId)
if (idx >= 0) selectedTagIds.value.splice(idx, 1)
@@ -1922,12 +1949,15 @@ onMounted(() => loadAll())
<el-form-item label="标签">
<div class="select-inline">
<el-select v-model="configForm.tagIds" multiple filterable placeholder="选择标签" style="flex:1">
<el-option-group v-for="g in groupedTagOptions" :key="g.id" :label="g.label">
<el-option-group v-for="g in configTagOptions" :key="g.id" :label="g.label">
<el-option v-for="t in g.tags" :key="t.id" :label="t.tagName" :value="t.id" />
</el-option-group>
</el-select>
<el-button text :icon="Plus" @click="quickCreateTag(() => { const latest = allTags[allTags.length - 1]; if (latest) configForm.tagIds.push(latest.id) })" />
</div>
<div v-if="configHasFamily" class="derived-tags-note">
物流 / 工艺 / 印刷位置标签由族成员自动生成无需手动选择
</div>
</el-form-item>
</el-form>
<template #footer>
@@ -2049,12 +2079,19 @@ onMounted(() => loadAll())
<el-form-item label="标签">
<div class="select-inline">
<el-select v-model="editForm.tagIds" multiple filterable placeholder="选择标签" style="flex:1">
<el-option-group v-for="g in groupedTagOptions" :key="g.id" :label="g.label">
<el-option-group v-for="g in editTagOptions" :key="g.id" :label="g.label">
<el-option v-for="t in g.tags" :key="t.id" :label="t.tagName" :value="t.id" />
</el-option-group>
</el-select>
<el-button text :icon="Plus" @click="quickCreateTag(() => { const latest = allTags[allTags.length - 1]; if (latest) editForm.tagIds.push(latest.id) })" />
</div>
<div v-if="editFamilyId && editDerivedTags.length" class="derived-tags-row">
<span class="derived-tags-label">族自动</span>
<el-tag v-for="t in editDerivedTags" :key="t.id" size="small" type="info" class="derived-tag">{{ t.tagName }}</el-tag>
</div>
<div v-else-if="editFamilyId" class="derived-tags-note">
物流 / 工艺 / 印刷位置标签由族成员自动生成无需手动选择
</div>
</el-form-item>
<el-form-item v-if="editIsCustom" label="基础价格">
<el-input v-model="customContentForm.goodPrice" placeholder="例如 28.00" />
@@ -2491,6 +2528,10 @@ onMounted(() => loadAll())
}
.edit-family-code { margin-left: auto; color: var(--el-color-primary); }
.edit-family-loading { color: var(--el-text-color-placeholder); font-size: 12px; padding: 4px 0; }
.derived-tags-note { font-size: 12px; color: var(--el-text-color-secondary); margin-top: 4px; }
.derived-tags-row { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.derived-tags-label { font-size: 12px; color: var(--el-text-color-secondary); }
.derived-tag { pointer-events: none; }
.edit-merged-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.edit-merged-item {
display: flex; align-items: center; gap: 8px;